package config

import (
	"fmt"
	"regexp"
	"time"

	"google.golang.org/protobuf/types/known/durationpb"

	secretstorev1 "github.com/tidbcloud/aisupport/packages/sdk-go/contracts/entity/agent-sidecar/secretstore/v1"
)

const (
	SecretSourceAWSSecretsManager = "awsSecretsManager"
	SecretSourceO11yExchange      = "o11yExchange"
	SecretSourceLarkTenantToken   = "larkTenantToken"
	SecretFormatOpaque            = "opaque"
	SecretFormatJSON              = "json"
	O11yEnvironmentProd           = "prod"
	O11yCloudProviderAWS          = "aws"
)

var logicalNamePattern = regexp.MustCompile(`^[a-z][a-z0-9-]{0,62}$`)

type SecretStoreServiceConfig struct {
	Runtimes map[string]RuntimeConfig `yaml:"runtimes"`
}

type RuntimeConfig struct {
	Source  SecretSourceConfig  `yaml:"source"`
	Refresh SecretRefreshConfig `yaml:"refresh"`
}

type SecretSourceConfig struct {
	LarkTenantToken   *LarkTenantTokenSourceConfig         `yaml:"larkTenantToken,omitempty"`
	Type              string                               `yaml:"type"`
	AWSSecretsManager *AWSSecretsManagerSecretSourceConfig `yaml:"awsSecretsManager,omitempty"`
	O11yExchange      *O11yExchangeSecretSourceConfig      `yaml:"o11yExchange,omitempty"`
}

type AWSSecretsManagerSecretSourceConfig struct {
	Region   string `yaml:"region"`
	SecretID string `yaml:"secretId"`
	Format   string `yaml:"format"`
}

type O11yExchangeSecretSourceConfig struct {
	Environment    string                `yaml:"environment"`
	Bootstrap      SecretReferenceConfig `yaml:"bootstrap"`
	CloudProvider  string                `yaml:"cloudProvider"`
	Endpoint       string                `yaml:"endpoint" default:"https://www.ds.us-west-2.aws.observability.tidbcloud.com/client/auth"`
	TokenTTL       time.Duration         `yaml:"tokenTTL" default:"12h"`
	RequestTimeout time.Duration         `yaml:"requestTimeout" default:"10s"`
}

type LarkTenantTokenSourceConfig struct {
	AppID          string                `yaml:"appId"`
	AppSecret      SecretReferenceConfig `yaml:"appSecret"`
	Brand          string                `yaml:"brand" default:"feishu"`
	RequestTimeout time.Duration         `yaml:"requestTimeout" default:"10s"`
}

func (c LarkTenantTokenSourceConfig) Origin() string {
	if c.Brand == "lark" {
		return "https://open.larksuite.com"
	}
	return "https://open.feishu.cn"
}

type SecretReferenceConfig struct {
	Secret string `yaml:"secret"`
	Field  string `yaml:"field"`
}

type SecretRefreshConfig struct {
	Interval            time.Duration `yaml:"interval"`
	RefreshBeforeExpiry time.Duration `yaml:"refreshBeforeExpiry"`
	MaxStaleness        time.Duration `yaml:"maxStaleness"`
}

func (c AWSSecretsManagerSecretSourceConfig) ContractFormat() secretstorev1.SecretFormat {
	switch c.Format {
	case SecretFormatOpaque:
		return secretstorev1.SecretFormat_SECRET_FORMAT_OPAQUE
	case SecretFormatJSON:
		return secretstorev1.SecretFormat_SECRET_FORMAT_JSON
	default:
		return secretstorev1.SecretFormat_SECRET_FORMAT_UNSPECIFIED
	}
}

func (c SecretSourceConfig) ContractFormat() secretstorev1.SecretFormat {
	if c.Type == SecretSourceAWSSecretsManager {
		return c.AWSSecretsManager.ContractFormat()
	}
	if c.Type == SecretSourceO11yExchange || c.Type == SecretSourceLarkTenantToken {
		return secretstorev1.SecretFormat_SECRET_FORMAT_JSON
	}
	return secretstorev1.SecretFormat_SECRET_FORMAT_UNSPECIFIED
}

func (c SecretSourceConfig) bootstrapSecret() string {
	if c.Type == SecretSourceLarkTenantToken {
		return c.LarkTenantToken.AppSecret.Secret
	}
	if c.Type == SecretSourceO11yExchange {
		return c.O11yExchange.Bootstrap.Secret
	}
	return ""
}

func (c SecretStoreServiceConfig) Validate() error {
	for name, runtime := range c.Runtimes {
		if !logicalNamePattern.MatchString(name) {
			return fmt.Errorf("runtime name %q is invalid", name)
		}
		if err := runtime.Source.validate(name, c.Runtimes); err != nil {
			return err
		}
		if err := runtime.Refresh.validate(name, runtime.Source); err != nil {
			return err
		}
	}
	if err := c.validateBootstrapReferences(); err != nil {
		return err
	}
	return nil
}

func (c SecretSourceConfig) validate(name string, runtimes map[string]RuntimeConfig) error {
	switch c.Type {
	case SecretSourceAWSSecretsManager:
		if c.AWSSecretsManager == nil || c.O11yExchange != nil || c.LarkTenantToken != nil {
			return fmt.Errorf("runtime %q must configure only source.awsSecretsManager", name)
		}
		if c.AWSSecretsManager.Region == "" {
			return fmt.Errorf("runtime %q must configure source.awsSecretsManager.region", name)
		}
		if c.AWSSecretsManager.SecretID == "" {
			return fmt.Errorf("runtime %q must configure source.awsSecretsManager.secretId", name)
		}
		if c.AWSSecretsManager.ContractFormat() == secretstorev1.SecretFormat_SECRET_FORMAT_UNSPECIFIED {
			return fmt.Errorf("runtime %q has unsupported source format %q", name, c.AWSSecretsManager.Format)
		}
	case SecretSourceO11yExchange:
		if c.O11yExchange == nil || c.AWSSecretsManager != nil || c.LarkTenantToken != nil {
			return fmt.Errorf("runtime %q must configure only source.o11yExchange", name)
		}
		if c.O11yExchange.Environment != O11yEnvironmentProd {
			return fmt.Errorf("runtime %q has unsupported o11y environment %q", name, c.O11yExchange.Environment)
		}
		if c.O11yExchange.CloudProvider != O11yCloudProviderAWS {
			return fmt.Errorf("runtime %q has unsupported o11y cloud provider %q", name, c.O11yExchange.CloudProvider)
		}
		if c.O11yExchange.Endpoint == "" {
			return fmt.Errorf("runtime %q must configure source.o11yExchange.endpoint", name)
		}
		if c.O11yExchange.TokenTTL <= 0 {
			return fmt.Errorf("runtime %q source.o11yExchange.tokenTTL must be positive", name)
		}
		if c.O11yExchange.RequestTimeout <= 0 {
			return fmt.Errorf("runtime %q source.o11yExchange.requestTimeout must be positive", name)
		}
		if c.O11yExchange.Bootstrap.Secret == "" || c.O11yExchange.Bootstrap.Field == "" {
			return fmt.Errorf("runtime %q must configure source.o11yExchange.bootstrap secret and field", name)
		}
		if _, ok := runtimes[c.O11yExchange.Bootstrap.Secret]; !ok {
			return fmt.Errorf("runtime %q references unknown bootstrap secret %q", name, c.O11yExchange.Bootstrap.Secret)
		}
	case SecretSourceLarkTenantToken:
		if c.LarkTenantToken == nil || c.AWSSecretsManager != nil || c.O11yExchange != nil {
			return fmt.Errorf("runtime %q must configure only source.larkTenantToken", name)
		}
		lark := c.LarkTenantToken
		if lark.AppID == "" || (lark.Brand != "feishu" && lark.Brand != "lark") || lark.RequestTimeout <= 0 {
			return fmt.Errorf("runtime %q must configure lark appId, brand and positive requestTimeout", name)
		}
		if err := validateSecretRef(APIProxySecretRefConfig{Secret: lark.AppSecret.Secret, Field: lark.AppSecret.Field}, SecretStoreServiceConfig{Runtimes: runtimes}); err != nil {
			return fmt.Errorf("runtime %q appSecret: %w", name, err)
		}
	default:
		return fmt.Errorf("runtime %q has unsupported source type %q", name, c.Type)
	}
	return nil
}

func (c SecretRefreshConfig) validate(name string, source SecretSourceConfig) error {
	if source.Type == SecretSourceLarkTenantToken {
		if c.Interval != 0 || c.MaxStaleness != 0 || c.RefreshBeforeExpiry <= 0 {
			return fmt.Errorf("runtime %q lark requires positive refreshBeforeExpiry and zero interval/maxStaleness", name)
		}
		return nil
	}
	if source.Type == SecretSourceO11yExchange {
		if c.Interval != 0 {
			return fmt.Errorf("runtime %q o11y refresh.interval must be zero", name)
		}
		if c.RefreshBeforeExpiry <= 0 {
			return fmt.Errorf("runtime %q refresh.refreshBeforeExpiry must be positive", name)
		}
		if c.RefreshBeforeExpiry >= source.O11yExchange.TokenTTL {
			return fmt.Errorf("runtime %q refresh.refreshBeforeExpiry must be shorter than the o11y token TTL", name)
		}
		if c.MaxStaleness != 0 {
			return fmt.Errorf("runtime %q o11y refresh.maxStaleness must be zero", name)
		}
		return nil
	}
	if c.Interval <= 0 {
		return fmt.Errorf("runtime %q refresh.interval must be positive", name)
	}
	if c.RefreshBeforeExpiry != 0 {
		return fmt.Errorf("runtime %q refresh.refreshBeforeExpiry must be zero", name)
	}
	if c.MaxStaleness <= 0 {
		return fmt.Errorf("runtime %q refresh.maxStaleness must be positive", name)
	}
	if c.MaxStaleness < c.Interval {
		return fmt.Errorf("runtime %q refresh.maxStaleness must not be shorter than refresh.interval", name)
	}
	return nil
}

func (c SecretStoreServiceConfig) validateBootstrapReferences() error {
	states := make(map[string]uint8, len(c.Runtimes))
	var visit func(string) error
	visit = func(name string) error {
		switch states[name] {
		case 1:
			return fmt.Errorf("bootstrap secret reference graph contains a cycle at %q", name)
		case 2:
			return nil
		}
		states[name] = 1
		if bootstrap := c.Runtimes[name].Source.bootstrapSecret(); bootstrap != "" {
			if err := visit(bootstrap); err != nil {
				return err
			}
		}
		states[name] = 2
		return nil
	}
	for name := range c.Runtimes {
		if err := visit(name); err != nil {
			return err
		}
	}
	return nil
}

// Contract projects only configured metadata and logical references, not source values.
func (c SecretSourceConfig) Contract() *secretstorev1.SecretSource {
	switch c.Type {
	case SecretSourceAWSSecretsManager:
		source := c.AWSSecretsManager
		return &secretstorev1.SecretSource{Config: &secretstorev1.SecretSource_AwsSecretsManager{
			AwsSecretsManager: &secretstorev1.AWSSecretsManagerSourceConfig{
				Region: source.Region, SecretId: source.SecretID, Format: source.ContractFormat(),
			},
		}}
	case SecretSourceO11yExchange:
		source := c.O11yExchange
		return &secretstorev1.SecretSource{Config: &secretstorev1.SecretSource_O11YExchange{
			O11YExchange: &secretstorev1.O11YExchangeSourceConfig{
				Environment: source.Environment, Bootstrap: source.Bootstrap.Contract(),
				CloudProvider: source.CloudProvider, Endpoint: source.Endpoint,
				TokenTtl: durationpb.New(source.TokenTTL), RequestTimeout: durationpb.New(source.RequestTimeout),
			},
		}}
	case SecretSourceLarkTenantToken:
		source := c.LarkTenantToken
		return &secretstorev1.SecretSource{Config: &secretstorev1.SecretSource_LarkTenantToken{
			LarkTenantToken: &secretstorev1.LarkTenantTokenSourceConfig{
				AppId: source.AppID, AppSecret: source.AppSecret.Contract(), Brand: source.Brand,
				RequestTimeout: durationpb.New(source.RequestTimeout),
			},
		}}
	default:
		return &secretstorev1.SecretSource{}
	}
}

func (c SecretReferenceConfig) Contract() *secretstorev1.SecretRef {
	return &secretstorev1.SecretRef{Name: c.Secret, Field: c.Field}
}

func (c SecretRefreshConfig) Contract() *secretstorev1.SecretRefresh {
	return &secretstorev1.SecretRefresh{
		Interval: durationpb.New(c.Interval), RefreshBeforeExpiry: durationpb.New(c.RefreshBeforeExpiry),
		MaxStaleness: durationpb.New(c.MaxStaleness),
	}
}
