package data

import (
	"fmt"

	"code.justin.tv/d8a/buddy/lib/awsutil"
	"code.justin.tv/d8a/buddy/lib/config"
	"code.justin.tv/d8a/buddy/lib/sandstorm"
	"code.justin.tv/d8a/buddy/lib/store"
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/ec2metadata"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/autoscaling"
	"github.com/aws/aws-sdk-go/service/autoscaling/autoscalingiface"
	"github.com/aws/aws-sdk-go/service/ec2"
	"github.com/aws/aws-sdk-go/service/ec2/ec2iface"
	"github.com/aws/aws-sdk-go/service/elasticache"
	"github.com/aws/aws-sdk-go/service/elasticache/elasticacheiface"
	"github.com/aws/aws-sdk-go/service/rds"
	"github.com/aws/aws-sdk-go/service/rds/rdsiface"
	"github.com/pkg/errors"
	"github.com/spf13/cobra"
)

var RootData *RootCommandData

type RootCommandData struct {
	regionFlag  string
	profileFlag string

	configFile      *config.ConfigFile
	sandstormClient sandstorm.SandstormAPI

	awsSession *session.Session

	rdsClient         rdsiface.RDSAPI
	ec2Client         ec2iface.EC2API
	elasticacheClient elasticacheiface.ElastiCacheAPI
	autoscalingClient autoscalingiface.AutoScalingAPI
}

func (data *RootCommandData) Region() string {
	return data.regionFlag
}

func (data *RootCommandData) Profile() string {
	return data.profileFlag
}

func (data *RootCommandData) ConfigFile() *config.ConfigFile {
	return data.configFile
}

func (data *RootCommandData) SandstormClient() sandstorm.SandstormAPI {
	return data.sandstormClient
}

func (data *RootCommandData) Session() *session.Session {
	return data.awsSession
}

func (data *RootCommandData) RdsClient() rdsiface.RDSAPI {
	return data.rdsClient
}

func (data *RootCommandData) Ec2Client() ec2iface.EC2API {
	return data.ec2Client
}

func (data *RootCommandData) ElasticacheClient() elasticacheiface.ElastiCacheAPI {
	return data.elasticacheClient
}

func (data *RootCommandData) AutoscalingClient() autoscalingiface.AutoScalingAPI {
	return data.autoscalingClient
}

func PopulateRootData(command *cobra.Command, args []string) error {
	profile := command.Flag("profile").Value.String()
	region := command.Flag("region").Value.String()

	path := config.FindConfigFile()
	if path == "" {
		return errors.New("could not locate `buddy.cfg` in the local directory or /etc/buddy")
	}

	var err error
	configFile, err := config.ReadConfig(path)
	if err != nil {
		return err
	}

	_, awsSession, err := awsutil.GetSession(region, profile, 8)
	if err != nil {
		return fmt.Errorf(fmt.Sprintf("couldn't get an aws session with region: %s and profile: %s", region, profile))
	}

	sandstormClient, err := sandstorm.New(configFile, region, configFile.SandstormRole, profile)
	if err != nil {
		return err
	}

	RootData = &RootCommandData{
		regionFlag:  region,
		profileFlag: profile,

		configFile:      configFile,
		sandstormClient: sandstormClient,

		awsSession: awsSession,

		rdsClient:         rds.New(awsSession),
		ec2Client:         ec2.New(awsSession),
		autoscalingClient: autoscaling.New(awsSession),
		elasticacheClient: elasticache.New(awsSession),
	}

	return nil
}

func (data *RootCommandData) LocateDataStore(isSetup bool) (*rds.DBInstance, error) {
	//If profile is not blank, that means we're on a non-amazon box & shouldn't try to get the local instance
	var localInstance *ec2.Instance
	var err error
	if data.Profile() == "" {
		metadataClient := ec2metadata.New(data.Session())
		localInstance, err = awsutil.FetchLocalInstance(data.Ec2Client(), metadataClient)
		if err != nil {
			return nil, errors.Wrap(err, "could not retrieve data store from metadata")
		}
	} else {
		if data.ConfigFile().StoreIdentifier == "" {
			return nil, errors.New("the buddy.cfg file does not contain a StoreIdentifier field, so you cannot interact with the datastore via a profile")
		}

		instances, err := awsutil.FetchInstancesByIds(data.RdsClient(), []*string{aws.String(data.ConfigFile().StoreIdentifier)})

		if err != nil {
			err = errors.Wrap(err, "could not retrieve data store with profile")
		}

		if len(instances) < 1 {
			return nil, err
		}

		return instances[0], err
	}

	return store.GetBuddyStore(data.RdsClient(), data.Ec2Client(), data.AutoscalingClient(), localInstance, data.SandstormClient(), data.ConfigFile(), isSetup)
}

func (data *RootCommandData) LocateRedis() (*elasticache.CacheCluster, error) {
	client := data.ElasticacheClient()

	result, err := client.DescribeCacheClusters(&elasticache.DescribeCacheClustersInput{
		CacheClusterId:    aws.String(fmt.Sprintf("buddy-%s", data.ConfigFile().SandstormTeam)),
		ShowCacheNodeInfo: aws.Bool(true),
	})
	if err != nil {
		return nil, err
	}

	if result != nil && len(result.CacheClusters) > 0 {
		return result.CacheClusters[0], nil
	}

	return nil, nil
}

func (data *RootCommandData) LocateKeyPair(keyPairName string) (bool, string, error) {
	keyPairExist := false

	svc := ec2.New(data.Session())
	input := &ec2.DescribeKeyPairsInput{KeyNames: []*string{aws.String(keyPairName),},}
	result, err := svc.DescribeKeyPairs(input)

	if err != nil {
		fmt.Printf("could not find key pair %s and buddy is going to create one \n", keyPairName)
		result, err := svc.CreateKeyPair(&ec2.CreateKeyPairInput{
			KeyName: aws.String(keyPairName),
		})

		if err != nil {
			fmt.Println(fmt.Sprintf("failed to create key pair %v", err))
			return keyPairExist, "", err
		}

		fmt.Printf("buddy created key pair %q %s\n", *result.KeyName, *result.KeyFingerprint)
		fmt.Printf("please keep a copy of the key material:\n%v\n", *result.KeyMaterial)

		return keyPairExist, *result.KeyMaterial, nil
	}
	keyPairExist = true
	for _, pair := range result.KeyPairs {
		fmt.Printf("key pair already exists: %s: %s\n", *pair.KeyName, *pair.KeyFingerprint)
	}
	return keyPairExist, "", nil
}
