package vo

import (
	"context"

	"github.com/tidbcloud/pkgv2/bizerr"
	"github.com/tidbcloud/serverless-service/idl/pbgen/proto/console"
	"github.com/tidbcloud/serverless-service/idl/pbgen/proto/v1beta1"
	"github.com/tidbcloud/serverless-service/internal/domain/constant"
	"github.com/tidbcloud/serverless-service/internal/domain/entity"
	"github.com/tidbcloud/serverless-service/internal/utils"
	"google.golang.org/protobuf/types/known/timestamppb"
)

type ListPrivateLinkConnectionOptions struct {
	ClusterID string                              `json:"cluster_id,omitempty"`
	Offset    int64                               `json:"offset,omitempty"`
	Limit     int32                               `json:"limit,omitempty"`
	OrderBy   string                              `json:"order_by,omitempty"`
	State     constant.PrivateLinkConnectionState `json:"state,omitempty"`
}

func PrivateLinkConnectionToV1beta1(pl *entity.PrivateLinkConnection) *v1beta1.PrivateLinkConnection {
	if pl == nil {
		return nil
	}
	out := &v1beta1.PrivateLinkConnection{
		PrivateLinkConnectionId: pl.ID,
		ClusterId:               pl.ClusterID,
		DisplayName:             pl.DisplayName,
		Message:                 pl.Message,
		CreatedBy:               pl.CreatedBy,
		CreateTime:              timestamppb.New(pl.CreatedAt),
	}
	if pl.UpdatedAt != nil {
		out.UpdateTime = timestamppb.New(*pl.UpdatedAt)
	}

	switch pl.State {
	case constant.PrivateLinkConnectionStateCreating:
		out.State = v1beta1.PrivateLinkConnectionState_CREATING
	case constant.PrivateLinkConnectionStateAvailable:
		out.State = v1beta1.PrivateLinkConnectionState_ACTIVE
	case constant.PrivateLinkConnectionStateDeleting:
		out.State = v1beta1.PrivateLinkConnectionState_DELETING
	case constant.PrivateLinkConnectionStateFailed:
		out.State = v1beta1.PrivateLinkConnectionState_FAILED
	case constant.PrivateLinkConnectionStatePendingAcceptance:
		out.State = v1beta1.PrivateLinkConnectionState_PENDING_ACCEPTANCE
	default:
		out.State = v1beta1.PrivateLinkConnectionState_UNSPECIFIED
	}

	switch pl.Type {
	case constant.PrivateLinkConnectionAWSEndpointService:
		out.Type = v1beta1.PrivateLinkConnectionType_AWS_ENDPOINT_SERVICE
		azs := make([]string, 0, len(pl.AWSEndpointService.EndpointAzIdToName))
		for azId := range pl.AWSEndpointService.EndpointAzIdToName {
			azs = append(azs, azId)
		}
		out.PrivateLinkConnectionType = &v1beta1.PrivateLinkConnection_AwsEndpointService{
			AwsEndpointService: &v1beta1.AwsEndpointService{
				Name:                pl.AWSEndpointServiceName,
				Region:              pl.AWSEndpointService.EndpointServiceRegion,
				EndpointId:          pl.AWSEndpointService.EndpointID,
				AvailabilityZoneIds: azs,
			},
		}

	case constant.PrivateLinkConnectionAlicloudEndpointService:
		out.Type = v1beta1.PrivateLinkConnectionType_ALICLOUD_ENDPOINT_SERVICE
		out.PrivateLinkConnectionType = &v1beta1.PrivateLinkConnection_AlicloudEndpointService{
			AlicloudEndpointService: &v1beta1.AlicloudEndpointService{
				Name:                pl.AlicloudEndpointServiceName,
				EndpointId:          pl.AlicloudEndpointService.EndpointID,
				AvailabilityZoneIds: pl.AlicloudEndpointService.EndpointAzs,
			},
		}
	case constant.PrivateLinkConnectionAmazonMSKProvisioned:
		out.Type = v1beta1.PrivateLinkConnectionType_AMAZON_MSK_PROVISIONED
		azs := make([]string, 0, len(pl.AmazonMSKProvisioned.EndpointAzIdToName))
		for azId := range pl.AmazonMSKProvisioned.EndpointAzIdToName {
			azs = append(azs, azId)
		}
		if pl.AmazonMSKProvisioned != nil {
			out.PrivateLinkConnectionType = &v1beta1.PrivateLinkConnection_AmazonMskProvisioned{
				AmazonMskProvisioned: &v1beta1.AmazonMSKProvisioned{
					MskArn:              pl.AmazonMSKProvisioned.MSKArn,
					MskVpcConnectionArn: pl.AmazonMSKProvisioned.MSKVPCConnectionArn,
					AvailabilityZoneIds: azs,
				},
			}
		}
	}
	return out
}

func PrivateLinkConnectionsStateFromV1beta1(
	state v1beta1.PrivateLinkConnectionState_Enum,
) constant.PrivateLinkConnectionState {
	switch state {
	case v1beta1.PrivateLinkConnectionState_CREATING:
		return constant.PrivateLinkConnectionStateCreating
	case v1beta1.PrivateLinkConnectionState_ACTIVE:
		return constant.PrivateLinkConnectionStateAvailable
	case v1beta1.PrivateLinkConnectionState_FAILED:
		return constant.PrivateLinkConnectionStateFailed
	case v1beta1.PrivateLinkConnectionState_PENDING_ACCEPTANCE:
		return constant.PrivateLinkConnectionStatePendingAcceptance
	case v1beta1.PrivateLinkConnectionState_DELETING:
		return constant.PrivateLinkConnectionStateDeleting
	}
	return ""
}

func V1beta1ToPrivateLinkConnection(
	ctx context.Context,
	pl *v1beta1.PrivateLinkConnection,
) (*entity.PrivateLinkConnection, bizerr.BizError) {
	if pl.DisplayName == "" {
		return nil, bizerr.New(bizerr.ErrCodeParams, "display name is required")
	}
	entityPL := &entity.PrivateLinkConnection{
		DisplayName: pl.DisplayName,
		CreatedBy:   utils.BuildCreatorID(ctx),
	}
	switch pl.Type {
	case v1beta1.PrivateLinkConnectionType_AWS_ENDPOINT_SERVICE:
		if pl.GetAwsEndpointService().GetName() == "" {
			return nil, bizerr.New(bizerr.ErrCodeParams, "AWS endpoint service name is required")
		}
		entityPL.Type = constant.PrivateLinkConnectionAWSEndpointService
		entityPL.AWSEndpointServiceName = pl.GetAwsEndpointService().GetName()
		entityPL.AWSEndpointService = &entity.AWSEndpointService{
			EndpointServiceName:   pl.GetAwsEndpointService().GetName(),
			EndpointServiceRegion: pl.GetAwsEndpointService().GetRegion(),
		}
	case v1beta1.PrivateLinkConnectionType_ALICLOUD_ENDPOINT_SERVICE:
		if pl.GetAlicloudEndpointService().GetName() == "" {
			return nil, bizerr.New(bizerr.ErrCodeParams, "AliCloud endpoint service name is required")
		}
		entityPL.Type = constant.PrivateLinkConnectionAlicloudEndpointService
		entityPL.AlicloudEndpointServiceName = pl.GetAlicloudEndpointService().GetName()
		entityPL.AlicloudEndpointService = &entity.AlicloudEndpointService{
			EndpointServiceName: pl.GetAlicloudEndpointService().GetName(),
		}
	case v1beta1.PrivateLinkConnectionType_AMAZON_MSK_PROVISIONED:
		if pl.GetAmazonMskProvisioned().GetMskArn() == "" {
			return nil, bizerr.New(bizerr.ErrCodeParams, "Amazon MSK ARN is required")
		}
		entityPL.Type = constant.PrivateLinkConnectionAmazonMSKProvisioned
		entityPL.AmazonMSKProvisionedArn = pl.GetAmazonMskProvisioned().GetMskArn()
		entityPL.AmazonMSKProvisioned = &entity.AmazonMSKProvisioned{
			MSKArn: pl.GetAmazonMskProvisioned().GetMskArn(),
		}
	default:
		return nil, bizerr.New(bizerr.ErrCodeParams, "unsupported private link connection type")
	}
	return entityPL, nil
}

func V1beta1ToAttachDomain(
	ctx context.Context,
	domain *v1beta1.AttachDomainsRequest,
) (*entity.PLCAttachDomain, bizerr.BizError) {
	if domain.PrivateLinkConnectionId == "" {
		return nil, bizerr.New(bizerr.ErrCodeParams, "private link connection id is required")
	}
	if domain.AttachDomain == nil {
		return nil, bizerr.New(bizerr.ErrCodeParams, "attach domain is required")
	}
	// only dry run for TiDB Cloud Managed domain can be empty unique name
	if domain.AttachDomain.UniqueName == "" &&
		(domain.AttachDomain.Type != v1beta1.PrivateLinkConnectionDomainType_TIDBCLOUD_MANAGED || !domain.ValidateOnly) {
		return nil, bizerr.New(bizerr.ErrCodeParams, "attach domain unique name is required")
	}

	entityDomain := &entity.PLCAttachDomain{
		ClusterID:               domain.ClusterId,
		PrivateLinkConnectionID: domain.PrivateLinkConnectionId,
		UniqueID:                domain.AttachDomain.UniqueName,
		CreatedBy:               utils.BuildCreatorID(ctx),
	}

	switch domain.AttachDomain.Type {
	case v1beta1.PrivateLinkConnectionDomainType_TIDBCLOUD_MANAGED:
		entityDomain.Type = constant.PLCAttachDomainTypeTiDBCloudManaged
	case v1beta1.PrivateLinkConnectionDomainType_CONFLUENT:
		entityDomain.Type = constant.PLCAttachDomainTypeConfluent
	default:
		return nil, bizerr.New(
			bizerr.ErrCodeParams,
			"unsupported attach domain type %s",
			domain.AttachDomain.Type.String(),
		)
	}
	return entityDomain, nil
}

func AttachDomainToV1beta1(
	ctx context.Context,
	attach *entity.PLCAttachDomain,
) *v1beta1.AttachDomain {
	attachDomain := &v1beta1.AttachDomain{
		AttachDomainId:          attach.ID,
		ClusterId:               attach.ClusterID,
		PrivateLinkConnectionId: attach.PrivateLinkConnectionID,
		UniqueName:              attach.UniqueID,
		CreatedBy:               attach.CreatedBy,
		CreateTime:              timestamppb.New(attach.CreatedAt),
	}

	switch attach.Type {
	case constant.PLCAttachDomainTypeTiDBCloudManaged:
		attachDomain.Type = v1beta1.PrivateLinkConnectionDomainType_TIDBCLOUD_MANAGED
	case constant.PLCAttachDomainTypeConfluent:
		attachDomain.Type = v1beta1.PrivateLinkConnectionDomainType_CONFLUENT
	}

	records := attach.Records
	domains := make([]*v1beta1.Domain, 0, len(records))
	for _, r := range records {
		domain := &v1beta1.Domain{
			Name:    r.Record,
			Message: r.Message,
			State:   string(r.State),
		}
		if domain.State == string(constant.PLCDomainRecordStateRecordsDeleted) {
			domain.State = string(constant.PLCDomainRecordStateDeleting)
		}
		domains = append(domains, domain)
	}
	attachDomain.Domains = domains
	return attachDomain
}

func PrivateLinkConnectionToConsole(pl *entity.PrivateLinkConnection) *console.PrivateLinkConnection {
	if pl == nil {
		return nil
	}
	out := &console.PrivateLinkConnection{
		PrivateLinkConnectionId: pl.ID,
		ClusterId:               pl.ClusterID,
		DisplayName:             pl.DisplayName,
		Message:                 pl.Message,
		CreatedBy:               pl.CreatedBy,
		CreateTime:              timestamppb.New(pl.CreatedAt),
	}
	if pl.UpdatedAt != nil {
		out.UpdateTime = timestamppb.New(*pl.UpdatedAt)
	}

	switch pl.State {
	case constant.PrivateLinkConnectionStateCreating:
		out.State = console.PrivateLinkConnectionState_CREATING
	case constant.PrivateLinkConnectionStateAvailable:
		out.State = console.PrivateLinkConnectionState_ACTIVE
	case constant.PrivateLinkConnectionStateDeleting:
		out.State = console.PrivateLinkConnectionState_DELETING
	case constant.PrivateLinkConnectionStateFailed:
		out.State = console.PrivateLinkConnectionState_FAILED
	case constant.PrivateLinkConnectionStatePendingAcceptance:
		out.State = console.PrivateLinkConnectionState_PENDING_ACCEPTANCE
	default:
		out.State = console.PrivateLinkConnectionState_UNSPECIFIED
	}

	switch pl.Type {
	case constant.PrivateLinkConnectionAWSEndpointService:
		out.Type = console.PrivateLinkConnectionType_AWS_ENDPOINT_SERVICE
		azs := make([]string, 0, len(pl.AWSEndpointService.EndpointAzIdToName))
		for azId := range pl.AWSEndpointService.EndpointAzIdToName {
			azs = append(azs, azId)
		}
		out.PrivateLinkConnectionType = &console.PrivateLinkConnection_AwsEndpointService{
			AwsEndpointService: &console.AwsEndpointService{
				Name:                pl.AWSEndpointServiceName,
				Region:              pl.AWSEndpointService.EndpointServiceRegion,
				EndpointId:          pl.AWSEndpointService.EndpointID,
				AvailabilityZoneIds: azs,
			},
		}

	case constant.PrivateLinkConnectionAlicloudEndpointService:
		out.Type = console.PrivateLinkConnectionType_ALICLOUD_ENDPOINT_SERVICE
		out.PrivateLinkConnectionType = &console.PrivateLinkConnection_AlicloudEndpointService{
			AlicloudEndpointService: &console.AlicloudEndpointService{
				Name:                pl.AlicloudEndpointServiceName,
				EndpointId:          pl.AlicloudEndpointService.EndpointID,
				AvailabilityZoneIds: pl.AlicloudEndpointService.EndpointAzs,
			},
		}
	case constant.PrivateLinkConnectionAmazonMSKProvisioned:
		out.Type = console.PrivateLinkConnectionType_AMAZON_MSK_PROVISIONED
		azs := make([]string, 0, len(pl.AmazonMSKProvisioned.EndpointAzIdToName))
		for azId := range pl.AmazonMSKProvisioned.EndpointAzIdToName {
			azs = append(azs, azId)
		}
		if pl.AmazonMSKProvisioned != nil {
			out.PrivateLinkConnectionType = &console.PrivateLinkConnection_AmazonMskProvisioned{
				AmazonMskProvisioned: &console.AmazonMSKProvisioned{
					MskArn:              pl.AmazonMSKProvisioned.MSKArn,
					MskVpcConnectionArn: pl.AmazonMSKProvisioned.MSKVPCConnectionArn,
					AvailabilityZoneIds: azs,
				},
			}
		}
	}
	return out
}

func PrivateLinkConnectionsStateFromConsole(
	state console.PrivateLinkConnectionState_Enum,
) constant.PrivateLinkConnectionState {
	switch state {
	case console.PrivateLinkConnectionState_CREATING:
		return constant.PrivateLinkConnectionStateCreating
	case console.PrivateLinkConnectionState_ACTIVE:
		return constant.PrivateLinkConnectionStateAvailable
	case console.PrivateLinkConnectionState_FAILED:
		return constant.PrivateLinkConnectionStateFailed
	case console.PrivateLinkConnectionState_PENDING_ACCEPTANCE:
		return constant.PrivateLinkConnectionStatePendingAcceptance
	case console.PrivateLinkConnectionState_DELETING:
		return constant.PrivateLinkConnectionStateDeleting
	}
	return ""
}

func ConsoleToPrivateLinkConnection(
	ctx context.Context,
	pl *console.PrivateLinkConnection,
) (*entity.PrivateLinkConnection, bizerr.BizError) {
	if pl == nil {
		return nil, bizerr.New(bizerr.ErrCodeParams, "empty private link connection")
	}
	if pl.DisplayName == "" {
		return nil, bizerr.New(bizerr.ErrCodeParams, "display name is required")
	}
	entityPL := &entity.PrivateLinkConnection{
		DisplayName: pl.DisplayName,
		CreatedBy:   utils.BuildCreatorID(ctx),
	}
	switch pl.Type {
	case console.PrivateLinkConnectionType_AWS_ENDPOINT_SERVICE:
		if pl.GetAwsEndpointService().GetName() == "" {
			return nil, bizerr.New(bizerr.ErrCodeParams, "AWS endpoint service name is required")
		}
		entityPL.Type = constant.PrivateLinkConnectionAWSEndpointService
		entityPL.AWSEndpointServiceName = pl.GetAwsEndpointService().GetName()
		entityPL.AWSEndpointService = &entity.AWSEndpointService{
			EndpointServiceName:   pl.GetAwsEndpointService().GetName(),
			EndpointServiceRegion: pl.GetAwsEndpointService().GetRegion(),
		}
	case console.PrivateLinkConnectionType_ALICLOUD_ENDPOINT_SERVICE:
		if pl.GetAlicloudEndpointService().GetName() == "" {
			return nil, bizerr.New(bizerr.ErrCodeParams, "AliCloud endpoint service name is required")
		}
		entityPL.Type = constant.PrivateLinkConnectionAlicloudEndpointService
		entityPL.AlicloudEndpointServiceName = pl.GetAlicloudEndpointService().GetName()
		entityPL.AlicloudEndpointService = &entity.AlicloudEndpointService{
			EndpointServiceName: pl.GetAlicloudEndpointService().GetName(),
		}
	case console.PrivateLinkConnectionType_AMAZON_MSK_PROVISIONED:
		if pl.GetAmazonMskProvisioned().GetMskArn() == "" {
			return nil, bizerr.New(bizerr.ErrCodeParams, "Amazon MSK ARN is required")
		}
		entityPL.Type = constant.PrivateLinkConnectionAmazonMSKProvisioned
		entityPL.AmazonMSKProvisionedArn = pl.GetAmazonMskProvisioned().GetMskArn()
		entityPL.AmazonMSKProvisioned = &entity.AmazonMSKProvisioned{
			MSKArn: pl.GetAmazonMskProvisioned().GetMskArn(),
		}
	default:
		return nil, bizerr.New(bizerr.ErrCodeParams, "unsupported private link connection type")
	}
	return entityPL, nil
}

func ConsoleToAttachDomain(
	ctx context.Context,
	domain *console.AttachDomainsRequest,
) (*entity.PLCAttachDomain, bizerr.BizError) {
	if domain == nil {
		return nil, bizerr.New(bizerr.ErrCodeParams, "empty request")
	}
	if domain.PrivateLinkConnectionId == "" {
		return nil, bizerr.New(bizerr.ErrCodeParams, "private link connection id is required")
	}
	if domain.AttachDomain == nil {
		return nil, bizerr.New(bizerr.ErrCodeParams, "attach domain is required")
	}
	// only dry run for TiDB Cloud Managed domain can be empty unique name
	if domain.AttachDomain.UniqueName == "" &&
		(domain.AttachDomain.Type != console.PrivateLinkConnectionDomainType_TIDBCLOUD_MANAGED || !domain.ValidateOnly) {
		return nil, bizerr.New(bizerr.ErrCodeParams, "attach domain unique name is required")
	}

	entityDomain := &entity.PLCAttachDomain{
		ClusterID:               domain.ClusterId,
		PrivateLinkConnectionID: domain.PrivateLinkConnectionId,
		UniqueID:                domain.AttachDomain.UniqueName,
		CreatedBy:               utils.BuildCreatorID(ctx),
	}

	switch domain.AttachDomain.Type {
	case console.PrivateLinkConnectionDomainType_TIDBCLOUD_MANAGED:
		entityDomain.Type = constant.PLCAttachDomainTypeTiDBCloudManaged
	case console.PrivateLinkConnectionDomainType_CONFLUENT:
		entityDomain.Type = constant.PLCAttachDomainTypeConfluent
	default:
		return nil, bizerr.New(
			bizerr.ErrCodeParams,
			"unsupported attach domain type %s",
			domain.AttachDomain.Type.String(),
		)
	}
	return entityDomain, nil
}

func AttachDomainToConsole(
	_ context.Context,
	attach *entity.PLCAttachDomain,
) *console.AttachDomain {
	if attach == nil {
		return nil
	}
	attachDomain := &console.AttachDomain{
		AttachDomainId:          attach.ID,
		ClusterId:               attach.ClusterID,
		PrivateLinkConnectionId: attach.PrivateLinkConnectionID,
		UniqueName:              attach.UniqueID,
		CreatedBy:               attach.CreatedBy,
		CreateTime:              timestamppb.New(attach.CreatedAt),
	}

	switch attach.Type {
	case constant.PLCAttachDomainTypeTiDBCloudManaged:
		attachDomain.Type = console.PrivateLinkConnectionDomainType_TIDBCLOUD_MANAGED
	case constant.PLCAttachDomainTypeConfluent:
		attachDomain.Type = console.PrivateLinkConnectionDomainType_CONFLUENT
	}

	records := attach.Records
	domains := make([]*console.Domain, 0, len(records))
	for _, r := range records {
		domain := &console.Domain{
			Name:    r.Record,
			Message: r.Message,
			State:   string(r.State),
		}
		if domain.State == string(constant.PLCDomainRecordStateRecordsDeleted) {
			domain.State = string(constant.PLCDomainRecordStateDeleting)
		}
		domains = append(domains, domain)
	}
	attachDomain.Domains = domains
	return attachDomain
}
