package account

import (
	"context"
	"fmt"

	"github.com/tidbcloud/infra-provider/pkg/k8s"
	"github.com/tidbcloud/infra-provider/regional-server/api/v1alpha1"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/common/constants"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/common/models"
	rsconfig "github.com/tidbcloud/infra-provider/regional-server/pkg/config"
	"gopkg.in/yaml.v3"
	v1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"sigs.k8s.io/controller-runtime/pkg/client"
)

type AccountReaderWriter interface {
	AccountReader
	AccountWriter
}

type AccountReader interface {
	GetDefault(ctx context.Context) (models.DataPlaneAccountInfo, error)
	GetByRef(ctx context.Context, ref *v1alpha1.AccountReference) (models.DataPlaneAccountInfo, error)
	List(ctx context.Context, opts ...models.ListAccountOption) ([]models.DataPlaneAccountInfo, error)
	GetBinding(ctx context.Context, target models.AccountBindingTarget) (models.DataPlaneAccountInfo, error)
}

type AccountWriter interface {
	Create(ctx context.Context, accountInfo *models.DataPlaneAccountInfo) error
	SetToDefault(ctx context.Context, accountInfo *models.DataPlaneAccountInfo) error
	Delete(ctx context.Context, accountInfo *models.DataPlaneAccountInfo) error

	Bind(ctx context.Context, binding models.DataPlaneAccountInfo, target models.AccountBindingTarget) error
	Unbind(ctx context.Context, target models.AccountBindingTarget) error
}

func NewAccountReaderWriter(cli client.Client, config rsconfig.RootConfig) AccountReaderWriter {
	return &accountReaderWriter{
		accountReader: accountReader{cli},
		accountWriter: accountWriter{cli, config},
	}
}

func NewAccountReader(cli client.Client) AccountReader {
	return &accountReader{
		readCli: cli,
	}
}

func NewAccountWriter(cli client.Client, config rsconfig.RootConfig) AccountWriter {
	return &accountWriter{
		cli:    cli,
		config: config,
	}
}

type accountReaderWriter struct {
	accountReader
	accountWriter
}

type accountWriter struct {
	cli    client.Client
	config rsconfig.RootConfig
}

func (a accountWriter) Create(ctx context.Context, accountInfo *models.DataPlaneAccountInfo) error {
	config := new(v1.ConfigMap)

	err := a.cli.Get(ctx, client.ObjectKey{
		Name:      constants.DataPlaneAccountConfigMapName,
		Namespace: constants.DataPlaneAccountNamespace,
	}, config)
	if err != nil {
		return err
	}

	_, accountList, err := parseAccountConfigMap(ctx, config)
	if err != nil {
		return err
	}
	for _, account := range accountList {
		if account.Is(accountInfo.GetKey()) {
			// duplicated account
			return nil
		}
	}
	accountList = append(accountList, *accountInfo)

	listYaml, err := yaml.Marshal(accountList)
	if err != nil {
		return fmt.Errorf("failed to marshal account list: %w", err)
	}
	config.Data[constants.DataPlaneAccountConfigMapListKey] = string(listYaml)

	err = a.cli.Update(ctx, config)
	if err != nil {
		return fmt.Errorf("failed to update account configmap(%s): %w", config.Name, err)
	}
	return nil
}

func (a accountWriter) SetToDefault(ctx context.Context, accountInfo *models.DataPlaneAccountInfo) error {
	config := new(v1.ConfigMap)

	err := a.cli.Get(ctx, client.ObjectKey{
		Name:      constants.DataPlaneAccountConfigMapName,
		Namespace: constants.DataPlaneAccountNamespace,
	}, config)
	if err != nil {
		return fmt.Errorf("failed to get account list: %w", err)
	}

	_, accountList, err := parseAccountConfigMap(ctx, config)
	if err != nil {
		return err
	}
	for _, account := range accountList {
		if account.Is(accountInfo.GetKey()) {
			config.Data[constants.DataPlaneAccountConfigMapDefaultKey] = accountInfo.GetKey()
			err = a.cli.Update(ctx, config)
			if err != nil {
				return fmt.Errorf("failed to update account configmap(%s): %w", config.Name, err)
			}
			return nil
		}
	}
	return fmt.Errorf("account(%s) is not found in accountList", accountInfo.GetKey())
}

func (a accountWriter) Delete(ctx context.Context, accountInfo *models.DataPlaneAccountInfo) error {
	//TODO implement me
	panic("implement me")
}

func (a accountWriter) Bind(ctx context.Context, account models.DataPlaneAccountInfo, target models.AccountBindingTarget) error {
	configData := map[string]string{}
	configData[constants.DataPlaneAccountConfigMapDefaultKey] = account.GetKey()

	configMap := &v1.ConfigMap{
		ObjectMeta: metav1.ObjectMeta{
			Namespace: constants.DataPlaneAccountNamespace,
			Name:      target.UniqueTargetName(),
			Labels:    a.config.ResourceLabels,
		},
		Data: configData,
	}
	return k8s.Apply(ctx, a.cli, configMap)
}

func (a accountWriter) Unbind(ctx context.Context, target models.AccountBindingTarget) error {
	configMap := &v1.ConfigMap{
		ObjectMeta: metav1.ObjectMeta{
			Namespace: constants.DataPlaneAccountNamespace,
			Name:      target.UniqueTargetName(),
		},
	}
	return client.IgnoreNotFound(a.cli.Delete(ctx, configMap))
}

type accountReader struct {
	readCli client.Reader
}

func (a accountReader) GetDefault(ctx context.Context) (models.DataPlaneAccountInfo, error) {
	defaultAccount, accountList, err := a.getAndParseAccountConfigMap(ctx)
	if err != nil {
		return models.DataPlaneAccountInfo{}, err
	}
	if len(defaultAccount) == 0 {
		return models.DataPlaneAccountInfo{}, fmt.Errorf("no default account found")
	}
	for _, account := range accountList {
		if account.Is(defaultAccount) {
			return account, nil
		}
	}
	return models.DataPlaneAccountInfo{}, fmt.Errorf("the default account(%s) is not found in accountList", defaultAccount)
}

func (a accountReader) GetByRef(ctx context.Context, ref *v1alpha1.AccountReference) (models.DataPlaneAccountInfo, error) {
	accountList, err := a.List(ctx)
	if err != nil {
		return models.DataPlaneAccountInfo{}, err
	}

	for _, account := range accountList {
		if account.Is(ref.AccountID) {
			return account, nil
		}
	}
	return models.DataPlaneAccountInfo{}, fmt.Errorf("ref.AccountID(%s) is not found in accountList", ref.AccountID)
}

func (a accountReader) List(ctx context.Context, opts ...models.ListAccountOption) ([]models.DataPlaneAccountInfo, error) {
	_, list, err := a.getAndParseAccountConfigMap(ctx)
	if err != nil {
		return nil, err
	}
	for _, filter := range opts {
		list = filter.Apply(list)
	}
	return list, nil
}

func (a accountReader) GetBinding(ctx context.Context, target models.AccountBindingTarget) (models.DataPlaneAccountInfo, error) {
	configMap := new(v1.ConfigMap)
	err := a.readCli.Get(ctx, client.ObjectKey{
		Namespace: constants.DataPlaneAccountNamespace,
		Name:      target.UniqueTargetName(),
	}, configMap)

	if err != nil {
		if client.IgnoreNotFound(err) != nil {
			return models.DataPlaneAccountInfo{}, err
		}
		return models.DataPlaneAccountInfo{}, nil
	}
	bindingAccountKey := configMap.Data[constants.DataPlaneAccountConfigMapDefaultKey]

	accountList, err := a.List(ctx)
	if err != nil {
		return models.DataPlaneAccountInfo{}, err
	}
	for _, account := range accountList {
		if account.Is(bindingAccountKey) {
			return account, nil
		}
	}
	return models.DataPlaneAccountInfo{}, fmt.Errorf("the binding account(%s) is not found in accountList", bindingAccountKey)
}

func (a accountReader) getAndParseAccountConfigMap(ctx context.Context) (string, []models.DataPlaneAccountInfo, error) {
	config := new(v1.ConfigMap)

	err := a.readCli.Get(ctx, client.ObjectKey{
		Name:      constants.DataPlaneAccountConfigMapName,
		Namespace: constants.DataPlaneAccountNamespace,
	}, config)
	if err != nil {
		return "", []models.DataPlaneAccountInfo{}, fmt.Errorf("failed to get account list: %w", err)
	}

	return parseAccountConfigMap(ctx, config)
}

func parseAccountConfigMap(ctx context.Context, config *v1.ConfigMap) (string, []models.DataPlaneAccountInfo, error) {
	var accountList []models.DataPlaneAccountInfo
	err := yaml.Unmarshal([]byte(config.Data[constants.DataPlaneAccountConfigMapListKey]), &accountList)
	if err != nil {
		return "", []models.DataPlaneAccountInfo{}, fmt.Errorf("failed to unmarshal account list: %w", err)
	}

	defaultAccount := config.Data[constants.DataPlaneAccountConfigMapDefaultKey]
	return defaultAccount, accountList, nil
}
