package registration

import (
	"context"
	"fmt"
	"net/http"
	"sort"

	"code.justin.tv/sse/malachai/pkg/internal/apierror"

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

const getServiceByNameKeyCondition = "service_id = :service_id"

// ErrServiceDoesNotExist is returned when a service is not found in the registration table
var ErrServiceDoesNotExist = apierror.NewError(http.StatusNotFound, "service not found")

// GetServiceByID retrieves a service by id
func (reg *registrar) GetServiceByID(serviceID string) (service *Service, err error) {
	params := &dynamodb.QueryInput{
		KeyConditionExpression: aws.String(getServiceByNameKeyCondition),
		ExpressionAttributeValues: map[string]*dynamodb.AttributeValue{
			":service_id": {S: aws.String(serviceID)},
		},
		IndexName: aws.String(serviceByIDIndex),
		TableName: aws.String(reg.cfg.TableName),
	}

	out, err := reg.db.Query(params)
	if err != nil {
		return
	}

	if aws.Int64Value(out.Count) < 1 {
		err = ErrServiceDoesNotExist
		return
	}

	if aws.Int64Value(out.Count) > 1 {
		err = fmt.Errorf("multiple services found with id %s", serviceID)
		return
	}

	return reg.unmarshalService(out.Items[0])
}

// Get retrieves a service by name
func (reg *registrar) Get(serviceName string) (service *Service, err error) {
	return reg.get(serviceName, &dynamodb.GetItemInput{})
}

// GetConsistent retrieves a service by name with consistent reads
func (reg *registrar) GetConsistent(serviceName string) (service *Service, err error) {
	return reg.get(serviceName, &dynamodb.GetItemInput{
		ConsistentRead: aws.Bool(true),
	})
}

func (reg *registrar) get(serviceName string, params *dynamodb.GetItemInput) (service *Service, err error) {
	params.SetKey(map[string]*dynamodb.AttributeValue{
		serviceNameKey: {S: aws.String(serviceName)},
	})
	params.SetTableName(reg.cfg.TableName)

	out, err := reg.db.GetItem(params)
	if err != nil {
		return
	}

	if out.Item == nil {
		err = ErrServiceDoesNotExist
		return
	}

	return reg.unmarshalService(out.Item)
}

// GetServicesOutput is the input to GetServices
type GetServicesOutput struct {
	Services []*Service
}

// GetServicesWithContext returns all services
// TODO: this should be paginated once we switch to a backend capable of
// pagination
func (reg *registrar) GetServicesWithContext(ctx context.Context) (output *GetServicesOutput, err error) {
	scanInput := &dynamodb.ScanInput{
		TableName: aws.String(reg.cfg.TableName),
	}

	output = &GetServicesOutput{
		Services: make([]*Service, 0),
	}

	err = reg.db.ScanPagesWithContext(ctx, scanInput, func(scanOutput *dynamodb.ScanOutput, lastPage bool) (cont bool) {
		for _, item := range scanOutput.Items {
			service, err := reg.unmarshalService(item)
			if err != nil {
				return
			}
			output.Services = append(output.Services, service)
		}

		cont = !lastPage
		return
	})

	sort.Sort(Sorter{
		Services: output.Services,
		By:       ByNameCaseInsensitive,
	})
	return
}

func (reg *registrar) unmarshalService(item map[string]*dynamodb.AttributeValue) (*Service, error) {
	var value Service
	if err := dynamodbattribute.UnmarshalMap(item, &value); err != nil {
		return nil, err
	}
	value.iam = reg.ig
	return &value, nil
}
