package models

import (
	"context"
	"fmt"

	"github.com/tidbcloud/infra-provider/regional-server/api/v1alpha1"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/common/constants"
)

type DataPlaneAccountInfo struct {
	SharedBucketBundle *SharedBucketBundle `yaml:"sharedBucketBundle"`

	*AWSAccountInfo
	*GCPAccountInfo
	*AzureAccountInfo
}

type SharedBucketBundle struct {
	DefaultBucketName string             `yaml:"defaultBucketName"`
	Buckets           []SharedBucketInfo `yaml:"buckets"`
}

func (a *SharedBucketBundle) GetDefault(ctx context.Context) (SharedBucketInfo, error) {
	if a == nil {
		return SharedBucketInfo{}, fmt.Errorf("the sharedBucketBundle is nil")
	}

	if len(a.DefaultBucketName) == 0 {
		// todo handle error if there is no default sharedBucket
		return SharedBucketInfo{}, fmt.Errorf("the default sharedBucket is empty")
	}

	bucket, err := a.GetByKey(ctx, a.DefaultBucketName)
	if err != nil {
		return SharedBucketInfo{}, fmt.Errorf("get the default sharedBucket(%s) failed: %w", a.DefaultBucketName, err)
	}
	return bucket, nil
}

func (a *SharedBucketBundle) GetByKey(ctx context.Context, bucketKey string) (SharedBucketInfo, error) {
	if a == nil {
		return SharedBucketInfo{}, fmt.Errorf("the sharedBucketBundle is nil")
	}

	for _, sharedBucket := range a.Buckets {
		if sharedBucket.Is(bucketKey) {
			return sharedBucket, nil
		}
	}
	// todo handle error if there is no sharedBucketInfo matched
	return SharedBucketInfo{}, fmt.Errorf("the sharedBucket(%s) is not found in list", bucketKey)
}

func (a *SharedBucketBundle) List(ctx context.Context, opts ...ListSharedBucketOption) ([]SharedBucketInfo, error) {
	if a == nil {
		return nil, nil
	}

	list := a.Buckets
	for _, filter := range opts {
		list = filter.Apply(list)
	}
	return list, nil
}

func (a *SharedBucketBundle) Create(ctx context.Context, sharedBucketInfo *SharedBucketInfo) error {

	for _, sharedBucket := range a.Buckets {
		if sharedBucket.Is(sharedBucketInfo.GetKey()) {
			// duplicated sharedBucket
			return nil
		}
	}
	a.Buckets = append(a.Buckets, *sharedBucketInfo)

	return nil
}

func (a *SharedBucketBundle) SetToDefault(ctx context.Context, sharedBucketKey string) error {
	for _, sharedBucket := range a.Buckets {
		if sharedBucket.Is(sharedBucketKey) {
			a.DefaultBucketName = sharedBucketKey
			return nil
		}
	}
	// todo error, sharedBucket not found
	return fmt.Errorf("sharedBucket(%s) not found", sharedBucketKey)
}

func (p DataPlaneAccountInfo) Is(key string) bool {
	if p.AWSAccountInfo != nil {
		return p.AWSAccountInfo.AWSAccount == key
	}
	if p.GCPAccountInfo != nil {
		return p.GCPAccountInfo.ProjectID == key
	}
	if p.AzureAccountInfo != nil {
		return p.AzureAccountInfo.SubscriptionID == key
	}
	return false
}

func (p DataPlaneAccountInfo) GetKey() string {
	if p.AWSAccountInfo != nil {
		return p.AWSAccountInfo.AWSAccount
	}
	if p.GCPAccountInfo != nil {
		return p.GCPAccountInfo.ProjectID
	}
	if p.AzureAccountInfo != nil {
		return p.AzureAccountInfo.SubscriptionID
	}
	return ""
}

func (p DataPlaneAccountInfo) GetAccountReference(providerConfigName string) *v1alpha1.AccountReference {
	accountRef := &v1alpha1.AccountReference{
		ProviderConfigName: providerConfigName,
	}
	if p.AWSAccountInfo != nil {
		accountRef.AccountID = p.AWSAccountInfo.AWSAccount
		accountRef.Role = p.AWSAccountInfo.AWSRole
	}
	if p.GCPAccountInfo != nil {
		accountRef.AccountID = p.GCPAccountInfo.ProjectID
	}
	if p.AzureAccountInfo != nil {
		accountRef.AccountID = p.AzureAccountInfo.SubscriptionID
	}

	return accountRef
}

func (p DataPlaneAccountInfo) GetAccountID() string {
	if p.AWSAccountInfo != nil {
		return p.AWSAccountInfo.AWSAccount
	}
	if p.GCPAccountInfo != nil {
		return p.GCPAccountInfo.ProjectID
	}
	if p.AzureAccountInfo != nil {
		return p.AzureAccountInfo.SubscriptionID
	}
	return ""
}

type AccountBindingTarget struct {
	TargetType constants.AccountBindingTargetType `yaml:"targetType"`
	TargetKey  string                             `yaml:"targetKey"`
}

func (a AccountBindingTarget) UniqueTargetName() string {
	return fmt.Sprintf("%s-%s", a.TargetType, a.TargetKey)
}

type AWSAccountInfo struct {
	AWSAccount  string `yaml:"awsAccount"`
	AWSRole     string `yaml:"awsRole"`
	CMEKEnabled bool   `yaml:"cmekEnabled"`
}

type GCPAccountInfo struct {
	ProjectID   string `yaml:"projectID"`
	CMEKEnabled bool   `yaml:"cmekEnabled"`
}

type AzureAccountInfo struct {
	SubscriptionID string `yaml:"subscriptionID"`
	CMEKEnabled    bool   `yaml:"cmekEnabled"`
}

type ListAccountOption interface {
	Apply(accounts []DataPlaneAccountInfo) []DataPlaneAccountInfo
}

type AccountFilter struct {
	CMEKEnabled bool
}

func (a AccountFilter) Apply(accounts []DataPlaneAccountInfo) []DataPlaneAccountInfo {
	results := make([]DataPlaneAccountInfo, 0)
	for _, account := range accounts {
		if account.AWSAccountInfo != nil && account.AWSAccountInfo.CMEKEnabled == a.CMEKEnabled {
			results = append(results, account)
		}
		if account.GCPAccountInfo != nil && account.GCPAccountInfo.CMEKEnabled == a.CMEKEnabled {
			results = append(results, account)
		}
		if account.AzureAccountInfo != nil {
			results = append(results, account)
		}
	}
	return results
}
