package callee

import (
	"errors"
	"fmt"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/cactus/go-statsd-client/statsd"

	"code.justin.tv/sse/malachai/pkg/capabilities"
	"code.justin.tv/sse/malachai/pkg/config"
	"code.justin.tv/sse/malachai/pkg/internal/inventory"
	"code.justin.tv/sse/malachai/pkg/internal/keystore"
	"code.justin.tv/sse/malachai/pkg/internal/statsdclient"
	"code.justin.tv/sse/malachai/pkg/registration"
	"code.justin.tv/sse/malachai/pkg/s2s/internal"
	"code.justin.tv/sse/malachai/pkg/signature"
)

// Options for QueueUnavailablePolicy
const (
	// Passthrough requests if we can't access updates
	// Reject requests if we can't access updates
	QueueUnavailablePolicyPassthrough = "passthrough"
	QueueUnavailablePolicyReject      = "reject"
)

// 32 MB read body limit
const defaultBodyLimit = 32 << 20

// Config holds configuration options for the callee
type Config struct {
	// Optional
	//
	// QueueUnavailablePolicy determines how the sdk handles problems with
	// the sqs queue for notifying on capability changes / pubkey revocations.
	// Defaults to `passthrough`
	//
	// QueueUnavailableRejectThreshold is the amount of time the sdk waits
	// until requests start getting rejected. Only applies when the sqs queue
	// is unreachable and QueueUnavailablePolicy is set to `reject`.
	QueueUnavailablePolicy          string
	QueueUnavailableRejectThreshold time.Duration

	// PassthroughMode accepts unsigned requests and requests that have
	// invalid signatures. This helps with identifying misconfigured clients
	// during onboarding.
	PassthroughMode bool

	// MaxRequestBodySize is the max bytes the s2s middleware will read from
	// the body of a request. Defaults to 32MB. If the body of the request
	// exceeds the configured maximum limit, the middleware will return an HTTP
	// 413 response status code.
	// https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/413
	MaxRequestBodySize int64

	// ExpiredPubkeyPurgeInterval is the interval for which the pubkey manager
	// purges the expired keys from local cache and tries to fetch the
	// replacement from the Discovery table. If unset, defaults to 24 hours.
	ExpiredPubkeyPurgeInterval time.Duration

	// optional path to s2s logging directory. if empty, defaults to
	// /var/log/malachai. Only used when bufferLogsInMemory is false.
	LogDir string

	// optional soft upper limit for number of bytes the in-memory buffer will
	// hold before starting to reject event writes. Set to 0 for unlimited.
	// Only used when bufferLogsInMemory is true.
	LogBufferSizeLimit int64

	// optional configuration if CapabilitiesAuthorizer should support wildcards in authorized paths. Defaults to off.
	// will negatively impact performance and scale directly with the number of paths provided.
	SupportWildCard bool

	// optional boolean to disable statsd client in dev environments without
	// connectivity to statsd.internal.justin.tv. Production clients should
	// have this set to false and configure proper access to the statsd
	// endpoint
	DisableStatsClient bool

	// optional aws config override
	AWSConfigBase *aws.Config

	// for internal use
	//
	// Environment should always be set to production or left empty
	Environment            string
	RequestValidatorConfig *signature.RequestValidatorConfig
	KeystoreConfig         *keystore.Config
	CapabilitiesConfig     *capabilities.Config
	RegistrationConfig     *registration.Config

	inventoryConfig *inventory.Config
	calleeCapConfig *calleeCapabilitiesConfig
	eventsConfig    *eventsConfig
	roleArn         string
	calleeID        string
	calleeName      string
}

func (cfg *Config) statsd() (statter statsd.Statter, err error) {
	return statsdclient.NewStatter(cfg.Environment, cfg.calleeName, cfg.DisableStatsClient)
}

// FillDefaults fills in default configuration options. This is called in
// the middleware constructors, so the client does not need to call this
// manually.
func (cfg *Config) FillDefaults() (err error) {
	if cfg.AWSConfigBase == nil {
		cfg.AWSConfigBase = aws.NewConfig()
	}

	if cfg.calleeName == "" {
		err = errors.New("calleeName is required")
		return
	}

	if cfg.MaxRequestBodySize == 0 {
		cfg.MaxRequestBodySize = defaultBodyLimit
	}

	if cfg.ExpiredPubkeyPurgeInterval == 0 {
		cfg.ExpiredPubkeyPurgeInterval = 24 * time.Hour
	}

	if cfg.Environment == "" {
		cfg.Environment = "production"
	}

	resources, err := config.GetResources(cfg.Environment)
	if err != nil {
		return
	}

	callee, err := internal.GetServiceRegistration(cfg.calleeName, cfg.Environment, cfg.AWSConfigBase)
	if err != nil {
		return
	}
	cfg.calleeID = callee.ID

	if cfg.eventsConfig == nil {
		cfg.eventsConfig = new(eventsConfig)
	}
	cfg.eventsConfig.Environment = cfg.Environment
	cfg.eventsConfig.ServiceID = callee.ID
	cfg.eventsConfig.RoleArn = callee.RoleArn
	cfg.eventsConfig.CalleeCapabilitiesTopicARN = callee.CapabilitiesTopicArn
	cfg.eventsConfig.AWSConfigBase = cfg.AWSConfigBase

	err = cfg.eventsConfig.FillDefaults()
	if err != nil {
		return
	}

	if cfg.KeystoreConfig == nil {
		cfg.KeystoreConfig = new(keystore.Config)
	}
	cfg.KeystoreConfig.Environment = cfg.Environment
	cfg.KeystoreConfig.RoleArn = callee.RoleArn
	cfg.KeystoreConfig.AWSConfigBase = cfg.AWSConfigBase
	err = cfg.KeystoreConfig.FillDefaults()
	if err != nil {
		return
	}

	if cfg.CapabilitiesConfig == nil {
		cfg.CapabilitiesConfig = new(capabilities.Config)
	}
	cfg.CapabilitiesConfig.Environment = cfg.Environment
	cfg.CapabilitiesConfig.RoleArn = callee.RoleArn
	cfg.CapabilitiesConfig.AWSConfigBase = cfg.AWSConfigBase
	err = cfg.CapabilitiesConfig.FillDefaults()
	if err != nil {
		return
	}

	if cfg.RegistrationConfig == nil {
		cfg.RegistrationConfig = &registration.Config{}
	}
	cfg.RegistrationConfig.Environment = cfg.Environment
	cfg.RegistrationConfig.RoleArn = resources.AuxiliaryRoleArn
	cfg.RegistrationConfig.AWSConfigBase = cfg.AWSConfigBase

	if cfg.calleeCapConfig == nil {
		cfg.calleeCapConfig = new(calleeCapabilitiesConfig)
	}
	cfg.calleeCapConfig.roleArn = callee.RoleArn
	cfg.calleeCapConfig.callee = callee.ID
	cfg.calleeCapConfig.FillDefaults()

	cfg.inventoryConfig = &inventory.Config{
		RoleArn:       callee.RoleArn,
		Environment:   cfg.Environment,
		AWSConfigBase: cfg.AWSConfigBase,
	}

	if cfg.QueueUnavailablePolicy == "" {
		cfg.QueueUnavailablePolicy = QueueUnavailablePolicyPassthrough
	}

	if cfg.RequestValidatorConfig == nil {
		cfg.RequestValidatorConfig = new(signature.RequestValidatorConfig)
	}

	cfg.RequestValidatorConfig.PassthroughMode = cfg.PassthroughMode

	err = cfg.validate()
	return
}

func (cfg *Config) validate() (err error) {
	switch cfg.QueueUnavailablePolicy {
	case QueueUnavailablePolicyPassthrough:
	case QueueUnavailablePolicyReject:
		if cfg.QueueUnavailableRejectThreshold <= 0 {
			err = errors.New("reject requires a QueueUnavailableRejectThreshold greater than 0")
			return
		}
	default:
		err = fmt.Errorf("invalid QueueUnavailablePolicy: '%s' must be one of 'passthrough' or 'reject'", cfg.QueueUnavailablePolicy)
		return
	}
	return
}
