package repository

import (
	"context"
	"encoding/json"
	"fmt"
	"log"
	"strconv"
	"time"

	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"github.com/tidbcloud/serverless-metering/pkg/config"
	"go.uber.org/zap"
	"k8s.io/apimachinery/pkg/runtime"
	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
	"k8s.io/client-go/tools/clientcmd"
	ctrl "sigs.k8s.io/controller-runtime"
	"sigs.k8s.io/controller-runtime/pkg/client"
	ctrlzap "sigs.k8s.io/controller-runtime/pkg/log/zap"
	"sigs.k8s.io/controller-runtime/pkg/manager"
	metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
)

const (
	InfraAPIClientDefaultTimeout = 60 * time.Second
	DefaultUnsafeDisableDeepCopy = true

	nameKey       = ".metadata.name"
	keyspaceIdKey = ".status.fromProvider.keyspaceId"

	BranchMetadataAnnKey = "branch.tidbcloud.com/metadata"
	BranchMainIDAnnKey   = "branch.tidbcloud.com/main-branch-id"
	LabelKeyRegion       = "cloud.pingcap.com/region"
	LabelKeyKubernetes   = "tags.tidbcloud.com/kubernetes"
)

type BranchMetadata struct {
	ID          string    `json:"id,omitempty"`
	Name        string    `json:"name,omitempty"`
	TenantID    uint64    `json:"tenant-id,omitempty"`
	ProjectID   uint64    `json:"project-id,omitempty"`
	CreatedAt   time.Time `json:"created-at,omitempty"`
	CreatedByID uint64    `json:"created-by-id,omitempty"`
	Salt        []byte    `json:"salt,omitempty"`
}

type InfraBranchRepo struct {
	region  string
	manager manager.Manager
	k8sName string
	client  client.Client
}

func NewInfraBranchRepo() (*InfraBranchRepo, error) {
	cfg := config.Global()
	mgr, err := runManager()
	if err != nil {
		return nil, err
	}
	return &InfraBranchRepo{
		manager: mgr,
		client:  mgr.GetClient(),
		region:  cfg.Region,
		k8sName: cfg.K8sName,
	}, nil
}

func (r *InfraBranchRepo) GetBranchByName(ctx context.Context, name string) (*ClusterBranch, error) {
	var branchList cloudv1.BranchList
	if err := r.client.List(
		ctx,
		&branchList,
		client.MatchingFields{nameKey: name},
		client.MatchingLabels{LabelKeyRegion: r.region, LabelKeyKubernetes: r.k8sName},
	); err != nil {
		return nil, fmt.Errorf("failed to list branch from infra: %s", err)
	}
	if len(branchList.Items) != 1 {
		return nil, fmt.Errorf("branch not found: %d", len(branchList.Items))
	}

	return r.convertInfraBranch(&branchList.Items[0])
}

func (r *InfraBranchRepo) GetBranchByKeyspaceID(ctx context.Context, keyspaceId string) (*ClusterBranch, error) {
	var branchList cloudv1.BranchList
	if err := r.client.List(
		ctx,
		&branchList,
		client.MatchingFields{keyspaceIdKey: keyspaceId},
		client.MatchingLabels{LabelKeyRegion: r.region, LabelKeyKubernetes: r.k8sName},
	); err != nil {
		return nil, fmt.Errorf("failed to list branch from infra: %s", err)
	}
	if len(branchList.Items) != 1 {
		return nil, fmt.Errorf("branch not found: %d", len(branchList.Items))
	}

	return r.convertInfraBranch(&branchList.Items[0])
}

func (r *InfraBranchRepo) convertInfraBranch(branch *cloudv1.Branch) (*ClusterBranch, error) {
	mainBranchID := branch.Annotations[BranchMainIDAnnKey]
	if mainBranchID == "" {
		return nil, fmt.Errorf("main branch annotation is empty")
	}
	clusterId, err := strconv.ParseUint(mainBranchID, 10, 64)
	if err != nil {
		return nil, fmt.Errorf("failed to parse main branch id: %s", err)
	}

	v := branch.Annotations[BranchMetadataAnnKey]
	if v == "" {
		return nil, fmt.Errorf("meta annotation is empty")
	}
	metadata := BranchMetadata{}
	if err := json.Unmarshal([]byte(v), &metadata); err != nil {
		return nil, err
	}

	encryptKeyspace := branch.Labels[cloudv1.LabelEncryptKeyspace] == "true"

	fromProvider, err := branch.AwsFreeTierClusterStatusFromBranch()
	if err != nil {
		return nil, fmt.Errorf("failed to get provider status: %s", err)
	}

	return &ClusterBranch{
		Type:            CBTypeBranch,
		PrimaryID:       branch.Name,
		Name:            metadata.Name,
		ClusterID:       clusterId,
		TenantID:        metadata.TenantID,
		ProjectID:       metadata.ProjectID,
		KeyspaceID:      fromProvider.KeyspaceId,
		KeyspaceToken:   fromProvider.KeyspaceToken,
		EncryptKeyspace: encryptKeyspace,
	}, nil
}

func runManager() (manager.Manager, error) {
	scheme := runtime.NewScheme()
	utilruntime.Must(cloudv1.AddToScheme(scheme))

	// switch to inClusterConfig
	restCfg, err := clientcmd.BuildConfigFromFlags("", "")
	if err != nil {
		return nil, fmt.Errorf("retrieve infra API server kubeconfig failed: %s", err)
	}
	restCfg.Timeout = InfraAPIClientDefaultTimeout

	opts := ctrlzap.Options{
		Development: true,
	}
	ctrl.SetLogger(ctrlzap.New(ctrlzap.UseFlagOptions(&opts)))
	mgr, err := ctrl.NewManager(restCfg,
		ctrl.Options{
			Scheme:         scheme,
			Metrics:        metricsserver.Options{BindAddress: "0"}, // disable metrics
			LeaderElection: false,
		},
	)
	if err != nil {
		return nil, fmt.Errorf("create controller-runtime manager failed: %s", err)
	}

	if err := (&BranchReconciler{}).SetupWithManager(mgr); err != nil {
		return nil, fmt.Errorf("failed to setup branch reconciler: %s", err)
	}

	go func() {
		if err := mgr.Start(context.TODO()); err != nil {
			log.Fatal("failed to start infra manager", zap.Error(err))
		}
	}()

	return mgr, nil
}

type BranchReconciler struct{}

func (r *BranchReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
	return ctrl.Result{}, nil
}

func (r *BranchReconciler) SetupWithManager(mgr ctrl.Manager) error {
	if err := mgr.GetFieldIndexer().IndexField(context.Background(), &cloudv1.Branch{}, nameKey, func(rawObj client.Object) []string {
		branch := rawObj.(*cloudv1.Branch)
		return []string{branch.Name}
	}); err != nil {
		return err
	}

	if err := mgr.GetFieldIndexer().IndexField(context.Background(), &cloudv1.Branch{}, keyspaceIdKey, func(rawObj client.Object) []string {
		branch := rawObj.(*cloudv1.Branch)
		fromProvider, err := branch.AwsFreeTierClusterStatusFromBranch()
		if err != nil {
			return []string{}
		}
		return []string{fmt.Sprintf("%d", fromProvider.KeyspaceId)}
	}); err != nil {
		return err
	}

	return ctrl.NewControllerManagedBy(mgr).For(&cloudv1.Branch{}).Complete(r)
}
