package euls

import (
	"errors"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/credentials"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/cognitoidentity"
	"github.com/aws/aws-sdk-go/service/mobileanalytics"
	"github.com/satori/go.uuid"
)

// Client interacts with AWS Mobile Analytics. Client handles authenticating
// to AWS given an identity pool, managing your session, and sending events.
type Client interface {
	StartSession() error
	StopSession() error
	PutEvent(eventType string, attributes map[string]*string, metrics map[string]*float64) error
}

type client struct {
	clientContextJSON []byte
	session           *mobileanalytics.Session

	ep eventPutter
}

// Config defines the configuration necessary for Client.
type Config struct {
	AccountID      string
	IdentityPoolID string
	AppID          string
	Client         ClientContext
	Env            EnvContext
}

type config struct {
	config  *Config
	session *session.Session
}

// NewClient creates a new Client given a Config.
func NewClient(config *Config) (Client, error) {
	return newClient(config, &syncEventPutter{})
}

func newClient(config *Config, ep eventPutter) (*client, error) {
	if ep == nil {
		return nil, errors.New("event putter cannot be nil")
	}

	if err := ep.SetConfig(config); err != nil {
		return nil, err
	}

	client := &client{
		ep: ep,
	}

	return client, nil
}

func newSession(config Config) (*session.Session, error) {
	sess, err := session.NewSession()
	if err != nil {
		return nil, err
	}

	sess.Config = sess.Config.WithRegion("us-east-1")

	identitySvc := cognitoidentity.New(sess)

	provider := &CognitoIdentityProvider{
		Svc:            identitySvc,
		IdentityPoolID: config.IdentityPoolID,
		AccountID:      config.AccountID,
	}

	sess.Config = sess.Config.WithCredentials(credentials.NewCredentials(provider))

	return sess, nil
}

func newContext(config Config) Context {
	context := Context{
		Client: config.Client,
		Env:    config.Env,
	}

	var zeroEnvContext EnvContext
	if context.Env == zeroEnvContext {
		context.Env = DefaultEnvContext()
	}

	context.Services.MobileAnalytics.AppID = config.AppID

	return context
}

// StartSession starts a new session by sending the corresponding start session
// event to Mobile Analytics. Once a session is started, it must be ended with
// StopSession before StartSession can be invoked again.
func (c *client) StartSession() error {
	if c == nil {
		return nil
	}

	if c.session != nil {
		return errors.New("session already in progress")
	}

	c.session = &mobileanalytics.Session{
		Id:             aws.String(uuid.NewV4().String()),
		StartTimestamp: aws.String(time.Now().Format(time.RFC3339)),
	}

	return c.PutEvent("_session.start", nil, nil)
}

// StopSession stops the session by sending the corresponding stop session
// event to MobileAnalytics. A session should be started with StartSession
// before this method is invoked.
func (c *client) StopSession() error {
	if c == nil {
		return nil
	}

	if c.session == nil {
		return errors.New("no current session")
	}

	endTime := time.Now()
	c.session.StopTimestamp = aws.String(endTime.Format(time.RFC3339))

	startTime, err := time.Parse(time.RFC3339, *c.session.StartTimestamp)
	if err != nil {
		return err
	}

	duration := endTime.Sub(startTime)

	c.session.Duration = aws.Int64(int64(duration.Seconds()))

	defer c.ep.Close()

	return c.PutEvent("_session.stop", nil, nil)
}

// PutEvent sends an event to Mobile Analytics given an event type, attributes,
// and metrics. A session must be created by StartSession for events to be sent.
func (c *client) PutEvent(eventType string, attributes map[string]*string, metrics map[string]*float64) error {
	if c.session == nil {
		return nil
	}

	event := &mobileanalytics.Event{ // Required
		EventType:  aws.String(eventType),
		Timestamp:  aws.String(time.Now().Format(time.RFC3339)),
		Attributes: attributes,
		Metrics:    metrics,
		Session:    c.session,
		Version:    aws.String("v2.0"), // Must be v2.0
	}

	_, err := c.ep.PutEvent(event)
	return err
}
