package inventory

import (
	"context"
	"time"

	"code.justin.tv/sse/malachai/pkg/config"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/dynamodb"
	"github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute"
	"github.com/aws/aws-sdk-go/service/dynamodb/dynamodbiface"
)

// API describes the inventory interface
type API interface {
	Put(in *Instance) (err error)
	ListServiceInstances(ctx context.Context, input *ListServiceInstancesInput) (output *ListServiceInstancesOutput, err error)
}

// Config holds inventory config options
type Config struct {
	Environment string
	Region      string
	RoleArn     string
	TableName   string

	AWSConfigBase *aws.Config
}

// Client implements inventory client
type Client struct {
	db     dynamodbiface.DynamoDBAPI
	config *Config
}

// Instance stores information about an s2s instance
type Instance struct {
	ServiceID              string
	ServiceName            string
	InstanceID             string
	Version                string
	PassthroughModeEnabled bool
	CreatedAt              time.Time
	UpdatedAt              time.Time
}

// MarshalDynamoDBMap converts Instance into a map[string]*dynamodb.Attributevalue
func (i *Instance) MarshalDynamoDBMap() (item map[string]*dynamodb.AttributeValue, err error) {
	item, err = dynamodbattribute.MarshalMap(&instance{
		ServiceID:              i.ServiceID,
		ServiceName:            i.ServiceName,
		InstanceID:             i.InstanceID,
		Version:                i.Version,
		PassthroughModeEnabled: i.PassthroughModeEnabled,
		CreatedAt:              dynamodbattribute.UnixTime(i.CreatedAt),
		UpdatedAt:              dynamodbattribute.UnixTime(i.UpdatedAt),
	})
	if err != nil {
		return
	}
	return
}

// UnmarshalDynamoDBMap sets attributes on Instance with the provided map[string]*dynamodb.AttributeValue
func (i *Instance) UnmarshalDynamoDBMap(item map[string]*dynamodb.AttributeValue) (err error) {
	in := &instance{}
	err = dynamodbattribute.UnmarshalMap(item, in)
	if err != nil {
		return
	}

	i.ServiceID = in.ServiceID
	i.ServiceName = in.ServiceName
	i.InstanceID = in.InstanceID
	i.Version = in.Version
	i.PassthroughModeEnabled = in.PassthroughModeEnabled
	i.CreatedAt = time.Time(in.CreatedAt)
	i.UpdatedAt = time.Time(in.UpdatedAt)
	return
}

type instance struct {
	ServiceID              string                     `dynamodbav:"service_id"`
	ServiceName            string                     `dynamodbav:"service_name"`
	InstanceID             string                     `dynamodbav:"instance_id"`
	Version                string                     `dynamodbav:"version"`
	PassthroughModeEnabled bool                       `dynamodbav:"passthrough_mode_enabled"`
	CreatedAt              dynamodbattribute.UnixTime `dynamodbav:"created_at"`
	UpdatedAt              dynamodbattribute.UnixTime `dynamodbav:"updated_at"`
}

func (cfg *Config) fillDefaults() (err error) {
	if cfg.Environment == "" {
		cfg.Environment = "production"
	}

	res, err := config.GetResources(cfg.Environment)
	if err != nil {
		return
	}
	if cfg.Region == "" {
		cfg.Region = res.Region
	}
	if cfg.TableName == "" {
		cfg.TableName = res.InventoryTableName
	}
	return
}

// New returns a new inventory client
func New(cfg *Config) (client *Client, err error) {
	err = cfg.fillDefaults()
	if err != nil {
		return
	}

	sess, err := session.NewSession(config.AWSConfig(cfg.AWSConfigBase, cfg.Region, cfg.RoleArn))
	ddb := dynamodb.New(sess)
	client = &Client{
		db:     ddb,
		config: cfg,
	}
	return
}

// Put updates a row in dynamodb
func (client *Client) Put(in *Instance) (err error) {
	now := time.Now().UTC()
	in.UpdatedAt = now

	err = client.updateItem(in)
	if err != nil {
		if err.(awserr.Error).Code() == dynamodb.ErrCodeConditionalCheckFailedException {
			// Record does not exist. Insert a new record.
			in.CreatedAt = now
			var item map[string]*dynamodb.AttributeValue
			item, err = in.MarshalDynamoDBMap()
			if err != nil {
				return
			}

			putInput := &dynamodb.PutItemInput{
				Item:      item,
				TableName: aws.String(client.config.TableName),
			}
			_, err = client.db.PutItem(putInput)
			return
		}
	}
	return
}

func (client *Client) updateItem(in *Instance) (err error) {
	updatedAtAV, err := dynamodbattribute.Marshal(in.UpdatedAt.Unix())
	if err != nil {
		return
	}

	input := &dynamodb.UpdateItemInput{
		ExpressionAttributeNames: map[string]*string{
			"#uat":     aws.String("updated_at"),
			"#sid":     aws.String("service_id"),
			"#iid":     aws.String("instance_id"),
			"#ptm":     aws.String("passthrough_mode_enabled"),
			"#version": aws.String("version"),
		},
		ExpressionAttributeValues: map[string]*dynamodb.AttributeValue{
			":uat": updatedAtAV,
			":sid": {
				S: aws.String(in.ServiceID),
			},
			":iid": {
				S: aws.String(in.InstanceID),
			},
			":ptm": {
				BOOL: aws.Bool(in.PassthroughModeEnabled),
			},
			":version": {
				S: aws.String(in.Version),
			},
		},
		Key: map[string]*dynamodb.AttributeValue{
			"service_id": {
				S: aws.String(in.ServiceID),
			},
			"instance_id": {
				S: aws.String(in.InstanceID),
			},
		},
		TableName:           aws.String(client.config.TableName),
		UpdateExpression:    aws.String("SET #uat = :uat, #ptm = :ptm, #version = :version"),
		ConditionExpression: aws.String("#sid = :sid AND #iid = :iid"),
	}

	_, err = client.db.UpdateItem(input)
	return
}
