package ecs

import (
	"context"
	"errors"
	"flag"
	"fmt"
	"log"

	"github.com/aws/aws-sdk-go/aws/credentials/stscreds"
	"github.com/google/subcommands"
	"github.com/sirupsen/logrus"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/ecs"
)

const (
	//ListenPort the port linux alb listens on
	listenPort = ":8000"
)

var (
	historyAccountID = map[string]string{
		"staging": "005087123760",
		"prod":    "958416494912",
	}
	subnets = map[string][]*string{
		"staging": {
			aws.String("subnet-ed93628a"),
			aws.String("subnet-24eb937c"),
			aws.String("subnet-39a9a14f"),
		},
		"prod": {
			aws.String("subnet-baaea6cc"),
			aws.String("subnet-7b99681c"),
			aws.String("subnet-83ef97db"),
		},
	}
	securityGroups = map[string][]*string{
		"staging": {
			aws.String("sg-564f482f"),
		},
		"prod": {
			aws.String("sg-815057f8"),
		},
	}
	historyEdgeURL = map[string]string{
		"staging": "005087123760.dkr.ecr.us-west-2.amazonaws.com/history-edge-staging",
		"prod":    "958416494912.dkr.ecr.us-west-2.amazonaws.com/history-edge-prod",
	}
)

// UpdateTaskDefCmd ...
type UpdateTaskDefCmd struct {
	Logger logrus.FieldLogger

	environment string
	gitCommit   string
	ecs         *ecs.ECS
}

// Name implement subcommands
func (svc *UpdateTaskDefCmd) Name() string {
	return "ecs"
}

// Synopsis implement subcommands
func (svc *UpdateTaskDefCmd) Synopsis() string {
	return "update all ecs task"
}

// Usage implement subcommands
func (svc *UpdateTaskDefCmd) Usage() string {
	return `ecs -env <'prod' or 'staging'> -git-commit <commit hash to be deployed on ecs>
	`
}

// SetFlags implement subcommands
func (svc *UpdateTaskDefCmd) SetFlags(f *flag.FlagSet) {
	f.StringVar(&svc.environment, "env", "", "one of 'staging' or 'prod'")
	f.StringVar(&svc.gitCommit, "git-commit", "", "git commit hash which need to be deployed to ecs")
}

//Execute implement subcommands
func (svc *UpdateTaskDefCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {

	if err := svc.init(); err != nil {
		svc.Logger.Error(err.Error())
		return subcommands.ExitFailure
	}

	err := svc.execute()
	if err != nil {
		svc.Logger.Error(err.Error())
		return subcommands.ExitFailure
	}
	return subcommands.ExitSuccess
}

func (svc *UpdateTaskDefCmd) init() error {
	if svc.environment == "" {
		return errors.New("arg 'env' must be provided")
	}

	if svc.gitCommit == "" {
		return errors.New("arg 'gitCommit' must be provided")
	}

	assumeRoleCreds := stscreds.NewCredentials(session.Must(session.NewSession(&aws.Config{Region: aws.String("us-west-2")})), fmt.Sprintf(
		"arn:aws:iam::%s:role/history-edge-%s-deploy",
		historyAccountID[svc.environment],
		svc.environment,
	))

	svc.ecs = ecs.New(session.Must(session.NewSession(&aws.Config{
		Region:      aws.String("us-west-2"),
		Credentials: assumeRoleCreds,
	})))

	return nil
}

func (svc *UpdateTaskDefCmd) getExecRoleArn() *string {
	return aws.String(fmt.Sprintf(
		"arn:aws:iam::%s:role/history-edge-%s-execution",
		historyAccountID[svc.environment],
		svc.environment,
	))
}

func (svc *UpdateTaskDefCmd) getTaskRoleArn() *string {
	return aws.String(fmt.Sprintf(
		"arn:aws:iam::%s:role/history-edge-%s-task",
		historyAccountID[svc.environment],
		svc.environment,
	))
}

func (svc *UpdateTaskDefCmd) createHistoryService() *ecs.ContainerDefinition {
	return &ecs.ContainerDefinition{
		Image:     aws.String(fmt.Sprintf("%s:%s", historyEdgeURL[svc.environment], svc.gitCommit)),
		Cpu:       aws.Int64(1024),
		Essential: aws.Bool(true),
		Memory:    aws.Int64(2048),
		Name:      aws.String(fmt.Sprintf("history-edge-%s", svc.environment)),
		LogConfiguration: &ecs.LogConfiguration{
			LogDriver: aws.String("awslogs"),
			Options: map[string]*string{
				"awslogs-group":         aws.String(fmt.Sprintf("history-edge/%s", svc.environment)),
				"awslogs-region":        aws.String("us-west-2"),
				"awslogs-stream-prefix": aws.String(svc.resourcePrefix()),
			},
		},
		Environment: []*ecs.KeyValuePair{
			{
				Name:  aws.String("ENVIRONMENT"),
				Value: aws.String(svc.environment),
			},
			{
				Name:  aws.String("BIND_ADDRESS"),
				Value: aws.String(listenPort),
			},
		},
		PortMappings: []*ecs.PortMapping{
			{
				ContainerPort: aws.Int64(8000),
			},
		},
	}
}

func (svc *UpdateTaskDefCmd) makeTaskDef() *ecs.RegisterTaskDefinitionInput {
	return &ecs.RegisterTaskDefinitionInput{
		ContainerDefinitions: []*ecs.ContainerDefinition{
			svc.createHistoryService(),
		},
		Cpu:              aws.String("1024"),
		Memory:           aws.String("2048"),
		Family:           aws.String(svc.resourcePrefix()),
		ExecutionRoleArn: svc.getExecRoleArn(),
		NetworkMode:      aws.String("awsvpc"),
		TaskRoleArn:      svc.getTaskRoleArn(),
	}
}

func (svc *UpdateTaskDefCmd) register() (*ecs.RegisterTaskDefinitionOutput, error) {
	return svc.ecs.RegisterTaskDefinition(svc.makeTaskDef())
}

func (svc *UpdateTaskDefCmd) update(taskDefinitionArn *string) (*ecs.UpdateServiceOutput, error) {
	return svc.ecs.UpdateService(&ecs.UpdateServiceInput{
		Cluster: aws.String(svc.resourcePrefix()),
		DeploymentConfiguration: &ecs.DeploymentConfiguration{
			MaximumPercent:        aws.Int64(200),
			MinimumHealthyPercent: aws.Int64(100),
		},
		Service:        aws.String(svc.resourcePrefix()),
		TaskDefinition: taskDefinitionArn,
		NetworkConfiguration: &ecs.NetworkConfiguration{
			AwsvpcConfiguration: &ecs.AwsVpcConfiguration{
				AssignPublicIp: aws.String("DISABLED"),
				Subnets:        subnets[svc.environment],
				SecurityGroups: securityGroups[svc.environment],
			},
		},
	})
}

func (svc *UpdateTaskDefCmd) resourcePrefix() string {
	return fmt.Sprintf("history-edge-%s", svc.environment)
}

func (svc *UpdateTaskDefCmd) execute() error {

	registeredTask, err := svc.register()
	if err != nil {
		return err
	}

	updatedTask, err := svc.update(registeredTask.TaskDefinition.TaskDefinitionArn)
	if err != nil {
		return err
	}

	log.Printf("%s updated at %s", *updatedTask.Service.ServiceName, *updatedTask.Service.CreatedAt)
	return nil
}
