package resource

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/aws/credentials"
	"github.com/aws/aws-sdk-go/aws/credentials/stscreds"
	"github.com/aws/aws-sdk-go/aws/endpoints"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/s3"
	"github.com/aws/aws-sdk-go/service/s3/s3iface"
	"github.com/aws/aws-sdk-go/service/sts"
)

const (
	defaultEnvironment         = "production"
	awsRegion                  = "us-west-2"
	s3SandstormBucketForConfig = "sse-sandstorm"
	readRole                   = "arn:aws:iam::516651178292:role/sandstorm/common/sandstorm-config-read"
)

// KMSKey holds KMS key region and arn available in sandstorm
type KMSKey struct {
	Region string `json:"region"`
	KeyARN string `json:"key_arn"`
}

// Config holds all the configuration read from s3 bucket
type Config struct {
	Environment    string `json:"environment"`
	AwsRegion      string `json:"aws_region"`
	TableName      string `json:"table_name"`
	KMSKeyID       string `json:"key_id"`
	RoleArn        string `json:"role_arn"`
	TopicArn       string `json:"topic_arn"`
	AuxPolicyArn   string `json:"aux_policy_arn"`
	RWAuxPolicyArn string `json:"rw_aux_policy_arn"`
	AccountID      string `json:"account_id"`

	InventoryRoleARN   string `json:"inventory_role_arn"`
	InventoryStatusURL string `json:"inventory_status_url"`

	SandstormTableArn      string `json:"sandstorm_table_arn"`
	SandstormAuditTableArn string `json:"sandstorm_audit_table_arn"`
	NamespaceTableArn      string `json:"namespaces_table_arn"`
	RoleOwnerTableName     string `json:"role_owners_table_name"`

	CloudwatchRoleArn string `json:"cloudwatch_role_arn"`

	// Resource for env: testing. set to "" for production.
	InventoryAdminRoleARN string `json:"inventory_admin_role_arn"`
	AgentTestingRoleArn   string `json:"agent_testing_role_arn"`
	KMSKey                KMSKey `json:"kms_key"`
}

// PutHeartbeatLambdaFunctionName ...
func (cfg Config) PutHeartbeatLambdaFunctionName() string {
	return "put_heartbeat-" + cfg.Environment
}

// ProductionConfig returns the production configuration
func ProductionConfig() *Config {
	return defaultResources()
}

func defaultResources() (cfg *Config) {
	cfg = &Config{
		Environment:            defaultEnvironment,
		AwsRegion:              "us-west-2",
		TableName:              "sandstorm-production",
		KMSKeyID:               "alias/sandstorm-production",
		RoleArn:                "arn:aws:iam::734326455073:role/sandstorm-apiserver-production",
		AuxPolicyArn:           "arn:aws:iam::734326455073:policy/sandstorm/production/aux_policy/sandstorm-agent-production-aux",
		RWAuxPolicyArn:         "arn:aws:iam::734326455073:policy/sandstorm/production/aux_policy/sandstorm-agent-production-rw-aux",
		TopicArn:               "arn:aws:sns:us-west-2:734326455073:sandstorm-production",
		InventoryRoleARN:       "arn:aws:iam::854594403332:role/inventory-gateway-execute-api-invoke-production",
		InventoryStatusURL:     "https://zhpqn2llm0.execute-api.us-west-2.amazonaws.com/production",
		SandstormTableArn:      "arn:aws:dynamodb:us-west-2:734326455073:table/sandstorm-production",
		SandstormAuditTableArn: "arn:aws:dynamodb:us-west-2:734326455073:table/sandstorm-production_audit",
		NamespaceTableArn:      "arn:aws:dynamodb:us-west-2:734326455073:table/sandstorm-production_namespaces",
		RoleOwnerTableName:     "sandstorm-production_role_owners",
		AccountID:              "734326455073",
		CloudwatchRoleArn:      "arn:aws:iam::734326455073:role/sandstorm/aux/sandstorm-cloudwatch-metrics-production",
		KMSKey: KMSKey{
			Region: "us-west-2",
			KeyARN: "arn:aws:kms:us-west-2:734326455073:key/60e0f97f-eeca-4834-b146-7ac9fb20fd74",
		},
	}
	return
}

// GetConfigForEnvironment loads all resources from file stored in s3 using the environment name,
func GetConfigForEnvironment(env string) (*Config, error) {

	if env == "" || env == defaultEnvironment {
		return defaultResources(), nil
	}

	svc := s3.New(session.New(&aws.Config{
		Region: aws.String(awsRegion),
		Credentials: credentials.NewCredentials(&stscreds.AssumeRoleProvider{
			Duration:     900 * time.Second,
			ExpiryWindow: 10 * time.Second,
			RoleARN:      readRole,
			Client: sts.New(session.New(&aws.Config{
				Region:              aws.String(awsRegion),
				STSRegionalEndpoint: endpoints.RegionalSTSEndpoint,
			})),
		}),
		STSRegionalEndpoint: endpoints.RegionalSTSEndpoint,
	}))
	conf, err := getConfigForEnvironment(svc, env)
	return conf, err
}

func getConfigForEnvironment(svc s3iface.S3API, env string) (res *Config, err error) {
	if cachedRes := loadCacheConfig(env); cachedRes != nil {
		res = cachedRes
		return
	}

	key := fmt.Sprintf("sandstorm-configs/%s.json", env)
	b, err := getS3ObjectAsBytes(svc, s3SandstormBucketForConfig, key)
	if err != nil {
		return
	}
	res = &Config{}
	err = json.Unmarshal(b, res)
	if err != nil {
		return
	}

	storeCacheConfig(env, res)
	return
}

func getS3ObjectAsBytes(svc s3iface.S3API, bucket, key string) (content []byte, err error) {

	input := &s3.GetObjectInput{
		Bucket: aws.String(bucket),
		Key:    aws.String(key),
	}

	result, err := svc.GetObject(input)
	if err != nil {
		if aerr, ok := err.(awserr.Error); ok {
			switch aerr.Code() {
			case s3.ErrCodeNoSuchKey:
				err = fmt.Errorf("object: %s/%s does not exist. err: %s", bucket, key, aerr.Error())
			default:
				err = fmt.Errorf("Error retrieving obj from s3: %s/%s , err: %s", bucket, key, aerr.Error())
			}
		}
		return
	}
	content, err = ioutil.ReadAll(result.Body)
	return
}
