// Package connprofile add by blacknc <blacknc@163.com> at 2024/12/17 16:01:33
package connprofile

import (
	"context"
	"errors"
	"fmt"
	"strconv"
	"strings"

	"github.com/AlekSi/pointer"
	perr "github.com/pingcap/errors"
	"github.com/tidbcloud/dataflow-service/app/container"
	"github.com/tidbcloud/dataflow-service/app/models/domain"
	"github.com/tidbcloud/dataflow-service/internal/db/entity"
	"github.com/tidbcloud/dataflow-service/internal/handler/agent"
	regionalapi "github.com/tidbcloud/dataflow-service/pkg/regional-api"
	"github.com/tidbcloud/eks-provider/api/v1alpha1"
	v1 "github.com/tidbcloud/infra-api/api/v1"
	ticdc_v1 "github.com/tidbcloud/infra-provider/api/tidbcloud/ticdc-service/v1"
	"github.com/tidbcloud/pkgv2/bizerr"
	"github.com/tidbcloud/pkgv2/log"
	"go.uber.org/zap"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

type Subresource string

const (
	StorageBlob Subresource = "blob"
)

var storagePrivateDNS = map[Subresource]string{
	StorageBlob: "privatelink.blob.core.windows.net",
}

func GetResourceAPI(ctx context.Context, tenantID uint64, provider, region string, isRegional bool) (ResourceAPI, error) {
	if isRegional {
		return NewTiCDCSindEndpointResourceAPI(container.Global().TiCDCPrivateLinkAPIClient, tenantID, region,
			string(domain.InfraProviderType2CloudProviderType(domain.InfraProviderType(provider)))), nil
	}
	return nil, errors.New("non-regional infra-api client is no longer supported")
}

type ResourceAPI interface {
	GetPLEndpoint(ctx context.Context, ns string, id entity.ID) (*v1alpha1.PrivateLinkEndpoint, error)
	DeletePLEndpointIgnoreNotFound(ctx context.Context, ns string, id entity.ID) error
	CreatePLEndpointIgnoreExists(ctx context.Context, id entity.ID, provider, region, ns, networkName, serviceName string, tenantID, projectID uint64) error
	IsNotFound(err error) bool
}

func NewRegionalResourceAPI(ctx context.Context, provider, region string, tenantID uint64) (ResourceAPI, error) {
	regionalAPIEndpoint, err := agent.BuildRegionalAPIEndpoint(ctx, provider, region, tenantID)
	if err != nil {
		log.WithContextL(ctx).Error("failed to build regional api endpoint", zap.Error(err))
		return nil, err
	}
	return &regionalResourceAPI{
		regionalAPI: regionalapi.NewTCIDMHTTPClient(regionalAPIEndpoint),
	}, nil
}

type regionalResourceAPI struct {
	regionalAPI regionalapi.TCIDMHTTPClient
}

func privateLinkEndpointObjName(id entity.ID) string {
	return fmt.Sprintf("df-pl-endpoint-%d", id)
}

func (a *regionalResourceAPI) GetPLEndpoint(ctx context.Context, ns string, id entity.ID) (*v1alpha1.PrivateLinkEndpoint, error) {
	name := privateLinkEndpointObjName(id)
	res, err := a.regionalAPI.GetPrivateLinkEndpoint(ctx, ns, name)
	if err != nil {
		return nil, perr.Trace(err)
	}
	return res, nil
}

func (a *regionalResourceAPI) DeletePLEndpointIgnoreNotFound(ctx context.Context, ns string, id entity.ID) error {
	name := privateLinkEndpointObjName(id)
	err := a.regionalAPI.DeletePrivateLinkEndpointIgnoreExists(ctx, ns, name)
	if err != nil {
		return perr.Trace(err)
	}
	return nil
}

func (a *regionalResourceAPI) CreatePLEndpointIgnoreExists(ctx context.Context, id entity.ID, provider, region, ns, networkName, serviceName string, tenantID, projectID uint64) error {
	name := privateLinkEndpointObjName(id)
	privateLinkEndpoint := v1alpha1.PrivateLinkEndpoint{
		ObjectMeta: metav1.ObjectMeta{
			Name:      name,
			Namespace: ns,
			Labels: map[string]string{
				v1.ProviderTypeLabel: provider,
				v1.RegionLabel:       region,
				v1.TenantTagKey:      strconv.FormatUint(tenantID, 10),
				v1.ProjectTagKey:     strconv.FormatUint(projectID, 10),
			},
		},
		Spec: v1alpha1.PrivateLinkEndpointSpec{
			Region:      region,
			ServiceName: serviceName,
			NetworkName: networkName,
		},
	}
	err := a.regionalAPI.CreatePrivateLinkEndpointIgnoreExists(ctx, &privateLinkEndpoint)
	if err != nil {
		return perr.Trace(err)
	}
	return nil
}

func (a *regionalResourceAPI) IsNotFound(err error) bool {
	return regionalapi.TCIDMIsNotFound(err)
}

type ticdcSindEndpointResourceAPI struct {
	cli           regionalapi.TiCDCPrivateLinkClient
	tenantID      uint64
	region        string
	cloudProvider string
}

func NewTiCDCSindEndpointResourceAPI(
	plCli regionalapi.TiCDCPrivateLinkClient, tenantID uint64, region string, provider string,
) ResourceAPI {
	return &ticdcSindEndpointResourceAPI{
		cli:           plCli,
		tenantID:      tenantID,
		region:        region,
		cloudProvider: provider,
	}
}

func (m *ticdcSindEndpointResourceAPI) GetPLEndpoint(
	ctx context.Context, ns string, id entity.ID,
) (*v1alpha1.PrivateLinkEndpoint, error) {
	region := &regionalapi.Region{
		TenantID:      m.tenantID,
		Name:          m.region,
		CloudProvider: m.cloudProvider,
	}
	req := &ticdc_v1.GetTiCDCSinkEndpointRequest{
		TicdcSinkEndpointId: m.genRegionalEndpointID(id.Uint64()),
	}
	endpoint, err := m.cli.GetTiCDCPrivateLink(ctx, region, req)
	if err != nil {
		return nil, err
	}
	return m.ticdcEndpoint2InfraEndpoint(endpoint), nil
}

func (m *ticdcSindEndpointResourceAPI) ticdcEndpoint2InfraEndpoint(
	cdcEndpoint *ticdc_v1.TiCDCSinkEndpoint,
) *v1alpha1.PrivateLinkEndpoint {
	resourceState := m.ticdcEndpointPhase2InfraEndpointState(cdcEndpoint.Phase)
	infraEndpoint := &v1alpha1.PrivateLinkEndpoint{
		ObjectMeta: metav1.ObjectMeta{
			Name: cdcEndpoint.EndpointId,
		},
		Spec: v1alpha1.PrivateLinkEndpointSpec{
			Region:      m.region,
			ServiceName: cdcEndpoint.ServiceName,
		},
		Status: v1alpha1.PrivateLinkEndpointStatus{
			Phase: m.ticdcEndpointPhase2InfraEndpointPhase(cdcEndpoint.Phase),
			AtProvider: v1alpha1.EndpointResource{
				EndpointID: pointer.ToString(cdcEndpoint.EndpointId),
				State:      &resourceState,
				DNSEntries: m.ticdcEndpoint2InfraDNSEntries(cdcEndpoint),
			},
		},
	}
	return infraEndpoint
}

func (m *ticdcSindEndpointResourceAPI) ticdcEndpointPhase2InfraEndpointPhase(
	cdcPhase ticdc_v1.TiCDCSinkEndpoint_Phase,
) v1alpha1.PrivateLinkEndpointPhase {
	switch cdcPhase {
	case ticdc_v1.TiCDCSinkEndpoint_CREATING:
		return v1alpha1.PrivateLinkEndpointPhaseCreating
	case ticdc_v1.TiCDCSinkEndpoint_DELETING:
		return v1alpha1.PrivateLinkEndpointPhaseDeleting
	case ticdc_v1.TiCDCSinkEndpoint_FAILED:
		return v1alpha1.PrivateLinkEndpointPhaseError
	default:
		return v1alpha1.PrivateLinkEndpointPhaseRunning
	}
}

func (m *ticdcSindEndpointResourceAPI) ticdcEndpointPhase2InfraEndpointState(
	cdcPhase ticdc_v1.TiCDCSinkEndpoint_Phase,
) v1alpha1.EndpointResourceState {
	switch cdcPhase {
	case ticdc_v1.TiCDCSinkEndpoint_CREATING:
		return v1alpha1.EndpointResourceStatePending
	case ticdc_v1.TiCDCSinkEndpoint_PENDING_ACCEPTANCE:
		return v1alpha1.EndpointResourceStatePendingAcceptance
	case ticdc_v1.TiCDCSinkEndpoint_AVAILABLE:
		return v1alpha1.EndpointResourceStateAvailable
	case ticdc_v1.TiCDCSinkEndpoint_REJECTED:
		return v1alpha1.EndpointResourceStateRejected
	case ticdc_v1.TiCDCSinkEndpoint_DELETING:
		return v1alpha1.EndpointResourceStateDeleting
	case ticdc_v1.TiCDCSinkEndpoint_FAILED:
		return v1alpha1.EndpointResourceStateFailed
	default:
		return v1alpha1.EndpointResourceStatePending
	}
}

func (m *ticdcSindEndpointResourceAPI) ticdcEndpoint2InfraDNSEntries(
	cdcEndpoint *ticdc_v1.TiCDCSinkEndpoint,
) []v1alpha1.DNSEntry {
	dnsEntries := make([]v1alpha1.DNSEntry, 0, len(cdcEndpoint.ZonesAddresses)+1)
	dnsEntries = append(dnsEntries, v1alpha1.DNSEntry{
		DNSName: pointer.ToString(cdcEndpoint.EndpointAddress),
	})
	for zone, address := range cdcEndpoint.ZonesAddresses {
		dnsEntries = append(dnsEntries, v1alpha1.DNSEntry{
			Zone:    pointer.ToString(zone),
			DNSName: pointer.ToString(address),
		})
	}
	return dnsEntries
}

func (m *ticdcSindEndpointResourceAPI) DeletePLEndpointIgnoreNotFound(
	ctx context.Context, ns string, id entity.ID,
) error {
	region := &regionalapi.Region{
		TenantID:      m.tenantID,
		Name:          m.region,
		CloudProvider: m.cloudProvider,
	}
	req := &ticdc_v1.DeleteTiCDCSinkEndpointRequest{
		TicdcSinkEndpointId: m.genRegionalEndpointID(id.Uint64()),
	}
	return m.cli.DeleteTiCDCPrivateLink(ctx, region, req)
}

func (m *ticdcSindEndpointResourceAPI) CreatePLEndpointIgnoreExists(
	ctx context.Context,
	id entity.ID,
	provider, region, ns, networkName, serviceName string,
	tenantID, projectID uint64,
) error {
	r := &regionalapi.Region{
		TenantID:      m.tenantID,
		Name:          m.region,
		CloudProvider: m.cloudProvider,
	}
	req := &ticdc_v1.CreateTiCDCSinkEndpointRequest{
		TicdcSinkEndpointId: m.genRegionalEndpointID(id.Uint64()),
		TicdcSinkEndpoint: &ticdc_v1.TiCDCSinkEndpoint{
			TenantId:       strconv.FormatUint(tenantID, 10),
			ProjectId:      strconv.FormatUint(projectID, 10),
			RootDomainName: m.getRootDomainName(serviceName),
			ServiceName:    serviceName,
		},
	}
	_, err := m.cli.CreateTiCDCPrivateLink(ctx, r, req)
	return err
}

func (m *ticdcSindEndpointResourceAPI) IsNotFound(err error) bool {
	var bizErr bizerr.BizError
	if errors.As(err, &bizErr) {
		return bizErr.ErrCode() == bizerr.ErrCodeRecordNotFound
	}
	return perr.IsNotFound(err)
}

func (m *ticdcSindEndpointResourceAPI) genRegionalEndpointID(id uint64) string {
	return strings.ToLower(fmt.Sprintf("df-pl-%d", id))
}

// getRootDomainName returns the Private DNS root domain used by Azure Private Link
// based on the target service resource name.
//
// For Azure Storage Account resources, the Blob service is exposed via
// the Private Link DNS zone:
//
//	privatelink.blob.core.windows.net
//
// TODO: support more dns resolve
func (m *ticdcSindEndpointResourceAPI) getRootDomainName(serviceName string) string {
	serviceLower := strings.ToLower(serviceName)
	// Example: /subscriptions/.../providers/Microsoft.Storage/storageAccounts/...
	if strings.Contains(serviceLower, "microsoft.storage/storageaccounts") {
		return storagePrivateDNS[StorageBlob]
	}
	return ""
}
