package component

import (
	"context"
	"fmt"
	"strconv"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"sigs.k8s.io/controller-runtime/pkg/client"

	eksv1alpha1 "github.com/tidbcloud/eks-provider/api/v1alpha1"
	"github.com/tidbcloud/eks-provider/controllers/tiflow/config"
	"github.com/tidbcloud/eks-provider/controllers/tiflow/task/cluster"
	"github.com/tidbcloud/eks-provider/controllers/tiflow/task/cluster/master"
	"github.com/tidbcloud/infra-provider/pkg/consts"
	"github.com/tidbcloud/infra-provider/pkg/gitops-sdk/utils"
	infrautil "github.com/tidbcloud/infra-provider/pkg/util"
)

const (
	localhost string = "127.0.0.1"
)

func NewMasterConfigQuirer(ctx *master.Context) config.ClusterConfigQuirer {
	return &MasterConfigQuirer{
		Context: ctx,
	}
}

type MasterConfigQuirer struct {
	*master.Context
}

func (t *MasterConfigQuirer) Execute(ctx context.Context) (*config.ClusterConfig, error) {
	baseQuirer := config.ConfigQuirerFactory(t.Config, true /*IsMaster*/)
	cc, err := baseQuirer.Execute(ctx)
	if err != nil {
		return nil, err
	}

	if err := t.overlayConfig(ctx, cc); err != nil {
		return nil, err
	}

	return cc, nil
}

func (t *MasterConfigQuirer) overlayConfig(ctx context.Context, cc *config.ClusterConfig) error {
	var masterConfig config.TiflowClusterMasterConfig
	if err := t.getMetaConfig(ctx, &masterConfig); err != nil {
		return err
	}
	t.fillBucketConfig(ctx, &masterConfig)

	tiflowMasterConfig, err := config.ConvertTiflowMasterConfig(&masterConfig)
	if err != nil {
		return err
	}

	if cc.Config == nil {
		cc.Config = tiflowMasterConfig
		return nil
	}

	// NOTE: local meta and s3 config will overlay gitops config
	cc.Config = utils.MergeMaps(cc.Config, tiflowMasterConfig)
	return nil
}

func (t *MasterConfigQuirer) getMetaConfig(ctx context.Context, masterConfig *config.TiflowClusterMasterConfig) error {
	endpoint, err := t.getMetaDbEndpoint(ctx)
	if err != nil {
		return err
	}

	masterConfig.FrameworkMeta = config.MetaDbConfig{
		Schema:    config.MetaDBFrameworkSchemaPrefix + infrautil.FnvHash(t.Namer.GetTiflowClusterName()),
		Endpoints: []string{endpoint + ":" + strconv.Itoa(t.Config.TiflowCluster.Master.ShareDb.Port)},
		User:      t.Config.TiflowCluster.Master.ShareDb.User,
		Password:  t.Config.TiflowCluster.Master.ShareDb.Password,
	}
	masterConfig.BusinessMeta = config.MetaDbConfig{
		Schema:    config.MetaDBBusinessMetaSchemaPrefix + infrautil.FnvHash(t.Namer.GetTiflowClusterName()),
		Endpoints: []string{endpoint + ":" + strconv.Itoa(t.Config.TiflowCluster.Master.ShareDb.Port)},
		User:      t.Config.TiflowCluster.Master.ShareDb.User,
		Password:  t.Config.TiflowCluster.Master.ShareDb.Password,
	}
	return nil
}

func (t *MasterConfigQuirer) fillBucketConfig(ctx context.Context, masterConfig *config.TiflowClusterMasterConfig) {
	bucketName := cluster.FormatBucketName(t.Config.FullManage.Project, t.Network, t.Namer.GetTiflowClusterName())
	if t.Network.Spec.Provider == consts.ProviderTypeGKE {
		masterConfig.Storage = config.Storage{
			GCS: &config.GCSConfig{
				Bucket: bucketName,
			},
		}
		return
	}
	masterConfig.Storage = config.Storage{
		S3: &config.S3Storage{
			Bucket: bucketName,
			Region: t.Network.Spec.Region,
			// No need to set roleArn here, we have injected some environment variables,like
			// AWS_WEB_IDENTITY_TOKEN_FILE,AWS_ROLE_ARN
		},
	}
}

func (t *MasterConfigQuirer) getMetaDbEndpoint(ctx context.Context) (dns string, err error) {
	if t.Network.Spec.Provider == consts.ProviderTypeGKE {
		if t.Config.TiflowCluster.Master.ShareDb.EnableProxy {
			// for gke, we use `cloud sql proxy` as the pod sidecar to securely access metadb
			// so we use `127.0.0.1` here
			return localhost, nil
		}

		// if we disable proxy for gke, we will use `ServiceName` as dns name or ip
		return t.Config.TiflowCluster.Master.ShareDb.ServiceName, nil
	}

	privateLinkEndpoint := &eksv1alpha1.PrivateLinkEndpoint{
		ObjectMeta: metav1.ObjectMeta{
			Name:      t.Namer.GetTiflowClusterName(),
			Namespace: t.TiflowClusterMaster.Namespace,
		},
	}
	err = t.Local.Get(ctx, client.ObjectKeyFromObject(privateLinkEndpoint), privateLinkEndpoint)
	if err != nil {
		return
	}

	for _, entry := range privateLinkEndpoint.Status.AtProvider.DNSEntries {
		if entry.Zone == nil {
			dns = *entry.DNSName
			return
		}
	}
	err = fmt.Errorf("cant find dns name for %s", privateLinkEndpoint.Name)
	return "", err
}
