package backend

import (
	"strconv"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/dynamodb"
	"golang.org/x/net/context"
)

const (
	recentlyUpdatedWindowHours = 24
)

type backend struct {
	db        *dynamodb.DynamoDB
	tableName string
}

func New(tableName string) (*backend, error) {
	sess := session.New(&aws.Config{
		Region: aws.String("us-west-2"),
	})

	b := &backend{
		db:        dynamodb.New(sess),
		tableName: tableName,
	}

	return b, nil
}

func (B *backend) EnableAnnotations(ctx context.Context, userID string) error {
	params := &dynamodb.UpdateItemInput{
		TableName: aws.String(B.tableName),
		Key: map[string]*dynamodb.AttributeValue{
			"user_id": &dynamodb.AttributeValue{
				S: aws.String(userID),
			},
		},
		UpdateExpression: aws.String("SET enabled=:value, last_updated=:t"),
		ExpressionAttributeValues: map[string]*dynamodb.AttributeValue{
			":value": &dynamodb.AttributeValue{
				BOOL: aws.Bool(true),
			},
			":t": &dynamodb.AttributeValue{
				S: aws.String(strconv.FormatInt(time.Now().UTC().Unix(), 10)),
			},
		},
		ReturnConsumedCapacity: aws.String(dynamodb.ReturnConsumedCapacityTotal),
	}

	_, err := B.db.UpdateItem(params)
	if err != nil {
		return err
	}

	return nil
}

func (B *backend) UpdateAnnotationsTimestamp(ctx context.Context, userID string) error {
	params := &dynamodb.UpdateItemInput{
		TableName: aws.String(B.tableName),
		Key: map[string]*dynamodb.AttributeValue{
			"user_id": &dynamodb.AttributeValue{
				S: aws.String(userID),
			},
		},
		UpdateExpression: aws.String("SET last_updated=:t"),
		ExpressionAttributeValues: map[string]*dynamodb.AttributeValue{
			":t": &dynamodb.AttributeValue{
				S: aws.String(strconv.FormatInt(time.Now().UTC().Unix(), 10)),
			},
		},
		ReturnConsumedCapacity: aws.String(dynamodb.ReturnConsumedCapacityTotal),
	}

	_, err := B.db.UpdateItem(params)
	if err != nil {
		return err
	}

	return nil
}

func (B *backend) DisableAnnotations(ctx context.Context, userID string) error {
	params := &dynamodb.DeleteItemInput{
		TableName: aws.String(B.tableName),
		Key: map[string]*dynamodb.AttributeValue{
			"user_id": &dynamodb.AttributeValue{
				S: aws.String(userID),
			},
		},
		ReturnConsumedCapacity: aws.String(dynamodb.ReturnConsumedCapacityTotal),
	}

	_, err := B.db.DeleteItem(params)
	if err != nil {
		return err
	}

	return nil
}

func (B *backend) GetAnnotationsEnabled(ctx context.Context, userID string) (bool, error) {
	params := &dynamodb.GetItemInput{
		TableName: aws.String(B.tableName),
		Key: map[string]*dynamodb.AttributeValue{
			"user_id": &dynamodb.AttributeValue{
				S: aws.String(userID),
			},
		},
		ConsistentRead:         aws.Bool(true),
		ReturnConsumedCapacity: aws.String(dynamodb.ReturnConsumedCapacityTotal),
	}

	resp, err := B.db.GetItem(params)
	if err != nil {
		return false, err
	}

	if resp.Item == nil {
		return false, nil
	}

	return true, nil
}

func (B *backend) ScanAnnotationsEnabledUsers() (chan string, chan error) {
	idCh := make(chan string, 1000)
	errCh := make(chan error)
	go func() {
		defer close(idCh)
		defer close(errCh)
		var key map[string]*dynamodb.AttributeValue
		for {
			input := &dynamodb.ScanInput{
				TableName:         aws.String(B.tableName),
				ExclusiveStartKey: key,
				Limit:             aws.Int64(100),
			}

			resp, err := B.db.Scan(input)
			if err != nil {
				errCh <- err
				return
			}

			for _, item := range resp.Items {
				if t, ok := item["last_updated"]; ok {
					timestamp, _ := strconv.ParseInt(*t.S, 10, 64)
					lastUpdated := time.Unix(timestamp, 0)
					if time.Since(lastUpdated).Hours() < recentlyUpdatedWindowHours {
						idCh <- *item["user_id"].S
					}
				}
			}

			key = resp.LastEvaluatedKey
			if key == nil || len(key) == 0 {
				return
			}
		}
	}()

	return idCh, errCh
}

func (B *backend) ScanAnnotationsEnabledUsersAll() (chan string, chan error) {
	idCh := make(chan string, 1000)
	errCh := make(chan error)
	go func() {
		defer close(idCh)
		defer close(errCh)
		var key map[string]*dynamodb.AttributeValue
		for {
			input := &dynamodb.ScanInput{
				TableName:         aws.String(B.tableName),
				ExclusiveStartKey: key,
				Limit:             aws.Int64(100),
			}

			resp, err := B.db.Scan(input)
			if err != nil {
				errCh <- err
				return
			}

			for _, item := range resp.Items {
				idCh <- *item["user_id"].S
			}

			key = resp.LastEvaluatedKey
			if key == nil || len(key) == 0 {
				return
			}
		}
	}()

	return idCh, errCh
}
