package vo

import (
	"fmt"
	"strconv"
	"time"

	"github.com/pingcap/errors"
	"github.com/tidbcloud/pkgv2/bizerr"
	"github.com/tidbcloud/serverless-service/idl/pbgen/proto/console"
	"github.com/tidbcloud/serverless-service/idl/pbgen/proto/rpc"
	"github.com/tidbcloud/serverless-service/idl/pbgen/proto/v1beta"
	"github.com/tidbcloud/serverless-service/idl/pbgen/proto/v1beta1"
	"github.com/tidbcloud/serverless-service/internal/domain/constant"
	"github.com/tidbcloud/serverless-service/internal/errorcode"
	"google.golang.org/protobuf/types/known/timestamppb"
)

type AnnotationKey string

const (
	CreatedBy      AnnotationKey = "created-by"
	HasSetPassword AnnotationKey = "tidb.cloud/has-set-password"
)

type Branch struct {
	ID                   string     `json:"id,omitempty"`
	CreatedAt            time.Time  `json:"created_at,omitempty"`
	CreationFinishedTime *time.Time `json:"creation_finished_time,omitempty"`
	UpdateAt             *time.Time `json:"updated_at,omitempty"`
	DeleteAt             *time.Time `json:"delete_at,omitempty"`

	TenantID          uint64 `json:"tenant_id,omitempty"`
	CreatedByID       uint64 `json:"created_by_id,omitempty"`
	CloudProviderName string `json:"cloud_provider_name,omitempty"`
	RegionName        string `json:"region_name,omitempty"`
	Kubernetes        string `json:"kubernetes,omitempty"`

	Name              string                `json:"name,omitempty"`
	Status            constant.BranchStatus `json:"status,omitempty"`
	ParentID          string                `json:"parent_id,omitempty"`
	ParentDisplayName string                `json:"parent_display_name,omitempty"`
	ParentTimestamp   *time.Time            `json:"parent_timestamp,omitempty"`
	MainBranchID      string                `json:"main_branch_id,omitempty"`
	DisplayName       string                `json:"display_name,omitempty"`
	AutoScaling       AutoScaling           `json:"auto_scaling,omitempty"`

	Endpoint   *Endpoint `json:"endpoint,omitempty"`
	UserPrefix string    `json:"user_prefix,omitempty"`
	KeyspaceId uint32    `json:"keyspace_id,omitempty"`

	EncryptedCloudAdminPwd string            `json:"encrypted_cloud_admin_pwd,omitempty"`
	HasSetPwd              bool              `json:"has_set_pwd,omitempty"`
	Annotations            map[string]string `json:"annotations,omitempty"`
	CreatedBy              string            `json:"created_by,omitempty"`
	InfraProjectId         uint64            `json:"infra_project_id,omitempty"`
}

type Endpoint struct {
	PublicEndpoint     *PublicEndpoint     `json:"public_endpoint,omitempty"`
	PrivateLinkService *PrivateLinkService `json:"private_link_service,omitempty"`
}

type PublicEndpoint struct {
	Host               string              `json:"host,omitempty"`
	Port               int32               `json:"port,omitempty"`
	AuthorizedNetworks []AuthorizedNetwork `json:"authorized_networks,omitempty"`
}

type AuthorizedNetwork struct {
	DisplayName    string `json:"display_name,omitempty"`
	StartIpAddress string `json:"start_ip_address,omitempty"`
	EndIpAddress   string `json:"end_ip_address,omitempty"`
}

type PrivateLinkService struct {
	Host string                 `json:"host,omitempty"`
	Port int32                  `json:"port,omitempty"`
	Aws  *AwsPrivateLinkService `json:"aws,omitempty"`
	Gcp  *GcpPrivateLinkService `json:"gcp,omitempty"`
}

type AwsPrivateLinkService struct {
	ServiceName       string   `json:"service_name,omitempty"`
	AvailabilityZones []string `json:"availability_zones,omitempty"`
}

type GcpPrivateLinkService struct {
	TargetServiceAttachment string `json:"target_service_attachment,omitempty"`
}

type ListBranchesOpt struct {
	Page            uint32
	PageSize        uint32
	PageTokenOffset int
	SortByTime      bool
	FullBranch      bool
}

type UpdateBranchReq struct {
	DisplayName string
}

func (b *Branch) ConvertToConsoleProtoFull() (*console.Branch, bizerr.BizError) {
	status := convertToConsoleStatus(b.Status)
	if status == console.BranchState_BRANCH_STATE_UNSPECIFIED {
		return nil, bizerr.New(errorcode.ErrCodeSvcBranch, errorcode.ErrMsgBranchStatusUnknown)
	}

	resp := &console.Branch{
		Id:                b.ID,
		Name:              b.Name,
		DisplayName:       b.DisplayName,
		ClusterId:         b.MainBranchID,
		ParentId:          b.ParentID,
		ParentDisplayName: b.ParentDisplayName,
		State:             status,
		CreateTime:        timestamppb.New(b.CreatedAt),
		HasSetPwd:         b.HasSetPwd,
		Annotations:       b.Annotations,
	}

	// updateTime
	if b.UpdateAt != nil {
		resp.UpdateTime = timestamppb.New(*b.UpdateAt)
	}

	// user prefix
	if b.UserPrefix != "" {
		resp.UserPrefix = b.UserPrefix
	}

	if b.Endpoint != nil {
		endpoint := &console.Endpoints{}
		if b.Endpoint.PublicEndpoint != nil {
			endpoint.PublicEndpoint = &console.PublicEndpoint{
				Host: b.Endpoint.PublicEndpoint.Host,
				Port: b.Endpoint.PublicEndpoint.Port,
				AuthorizedNetworks: ConvertAuthorizedNetworks(
					b.Endpoint.PublicEndpoint.AuthorizedNetworks,
					toConsoleAuthorizedNetwork,
				),
			}
		}
		if b.Endpoint.PrivateLinkService != nil {
			endpoint.PrivateEndpoint = &console.PrivateEndpoint{}
			if b.Endpoint.PrivateLinkService.Aws != nil {
				endpoint.PrivateEndpoint.Host = b.Endpoint.PrivateLinkService.Host
				endpoint.PrivateEndpoint.Port = b.Endpoint.PrivateLinkService.Port
				endpoint.PrivateEndpoint.Provider = &console.PrivateEndpoint_Aws{
					Aws: &console.PrivateEndpointAws{
						ServiceMame:      b.Endpoint.PrivateLinkService.Aws.ServiceName,
						AvailabilityZone: b.Endpoint.PrivateLinkService.Aws.AvailabilityZones,
					},
				}
			}
			if b.Endpoint.PrivateLinkService.Gcp != nil {
				endpoint.PrivateEndpoint.Host = b.Endpoint.PrivateLinkService.Host
				endpoint.PrivateEndpoint.Port = b.Endpoint.PrivateLinkService.Port
				endpoint.PrivateEndpoint.Provider = &console.PrivateEndpoint_Gcp{
					Gcp: &console.PrivateEndpointGcp{
						TargetServiceAttachment: b.Endpoint.PrivateLinkService.Gcp.TargetServiceAttachment,
					},
				}
			}
		}
		resp.Endpoints = endpoint
	}

	if b.ParentTimestamp != nil {
		resp.ParentTimestamp = timestamppb.New(*b.ParentTimestamp)
	}

	return resp, nil
}

func (b *Branch) ConvertToConsoleProtoBasic() (*console.BasicBranch, bizerr.BizError) {
	status := convertToConsoleStatus(b.Status)
	if status == console.BranchState_BRANCH_STATE_UNSPECIFIED {
		return nil, bizerr.New(errorcode.ErrCodeSvcBranch, errorcode.ErrMsgBranchStatusUnknown)
	}

	resp := &console.BasicBranch{
		Id:                b.ID,
		Name:              b.Name,
		DisplayName:       b.DisplayName,
		ClusterId:         b.MainBranchID,
		ParentId:          b.ParentID,
		ParentDisplayName: b.ParentDisplayName,
		State:             status,
		CreateTime:        timestamppb.New(b.CreatedAt),
		HasSetPwd:         b.HasSetPwd,
	}
	// updateTime
	if b.UpdateAt != nil {
		resp.UpdateTime = timestamppb.New(*b.UpdateAt)
	}
	// user prefix
	if b.UserPrefix != "" {
		resp.UserPrefix = b.UserPrefix
	}
	// endPoint
	if b.Endpoint != nil {
		endpoint := &console.Endpoints{}
		if b.Endpoint.PublicEndpoint != nil {
			endpoint.PublicEndpoint = &console.PublicEndpoint{
				Host: b.Endpoint.PublicEndpoint.Host,
				Port: b.Endpoint.PublicEndpoint.Port,
				AuthorizedNetworks: ConvertAuthorizedNetworks(
					b.Endpoint.PublicEndpoint.AuthorizedNetworks,
					toConsoleAuthorizedNetwork,
				),
			}
		}
		if b.Endpoint.PrivateLinkService != nil {
			endpoint.PrivateEndpoint = &console.PrivateEndpoint{}
			if b.Endpoint.PrivateLinkService.Aws != nil {
				endpoint.PrivateEndpoint.Host = b.Endpoint.PrivateLinkService.Host
				endpoint.PrivateEndpoint.Port = b.Endpoint.PrivateLinkService.Port
				endpoint.PrivateEndpoint.Provider = &console.PrivateEndpoint_Aws{
					Aws: &console.PrivateEndpointAws{
						ServiceMame:      b.Endpoint.PrivateLinkService.Aws.ServiceName,
						AvailabilityZone: b.Endpoint.PrivateLinkService.Aws.AvailabilityZones,
					},
				}
			}
			if b.Endpoint.PrivateLinkService.Gcp != nil {
				endpoint.PrivateEndpoint.Host = b.Endpoint.PrivateLinkService.Host
				endpoint.PrivateEndpoint.Port = b.Endpoint.PrivateLinkService.Port
				endpoint.PrivateEndpoint.Provider = &console.PrivateEndpoint_Gcp{
					Gcp: &console.PrivateEndpointGcp{
						TargetServiceAttachment: b.Endpoint.PrivateLinkService.Gcp.TargetServiceAttachment,
					},
				}
			}
		}
		resp.Endpoints = endpoint
	}

	if b.ParentTimestamp != nil {
		resp.ParentTimestamp = timestamppb.New(*b.ParentTimestamp)
	}
	return resp, nil
}

func (b *Branch) ConvertToRpcProtoFull() (*rpc.FullBranch, error) {
	status := convertToRpcStatus(b.Status)
	if status == rpc.BranchStatus_BRANCH_STATE_UNSPECIFIED {
		return nil, errors.Errorf("unknown branch status: %s", b.Status)
	}

	branch := &rpc.FullBranch{
		Id:                b.ID,
		Name:              b.Name,
		DisplayName:       b.DisplayName,
		ClusterId:         b.MainBranchID,
		ParentId:          b.ParentID,
		ParentDisplayName: b.ParentDisplayName,
		State:             status,
		CreateTime:        timestamppb.New(b.CreatedAt),
		Annotations:       b.Annotations,
	}
	// updateTime
	if b.UpdateAt != nil {
		branch.UpdateTime = timestamppb.New(*b.UpdateAt)
	}
	// user prefix
	if b.UserPrefix != "" {
		branch.UserPrefix = b.UserPrefix
	}
	if b.CreationFinishedTime != nil {
		branch.CreationFinishedTime = timestamppb.New(*b.CreationFinishedTime)
	}
	// endPoint
	if b.Endpoint != nil {
		endpoint := &rpc.Endpoints{}
		if b.Endpoint.PublicEndpoint != nil {
			endpoint.PublicEndpoint = &rpc.PublicEndpoint{
				Host: b.Endpoint.PublicEndpoint.Host,
				Port: b.Endpoint.PublicEndpoint.Port,
				AuthorizedNetworks: ConvertAuthorizedNetworks(
					b.Endpoint.PublicEndpoint.AuthorizedNetworks,
					toRpcAuthorizedNetwork,
				),
			}
		}
		if b.Endpoint.PrivateLinkService != nil {
			endpoint.PrivateEndpoint = &rpc.PrivateEndpoint{}
			if b.Endpoint.PrivateLinkService.Aws != nil {
				endpoint.PrivateEndpoint.Host = b.Endpoint.PrivateLinkService.Host
				endpoint.PrivateEndpoint.Port = b.Endpoint.PrivateLinkService.Port
				endpoint.PrivateEndpoint.Provider = &rpc.PrivateEndpoint_Aws{
					Aws: &rpc.PrivateEndpointAws{
						ServiceMame:      b.Endpoint.PrivateLinkService.Aws.ServiceName,
						AvailabilityZone: b.Endpoint.PrivateLinkService.Aws.AvailabilityZones,
					},
				}
			}
			if b.Endpoint.PrivateLinkService.Gcp != nil {
				endpoint.PrivateEndpoint.Host = b.Endpoint.PrivateLinkService.Host
				endpoint.PrivateEndpoint.Port = b.Endpoint.PrivateLinkService.Port
				endpoint.PrivateEndpoint.Provider = &rpc.PrivateEndpoint_Gcp{
					Gcp: &rpc.PrivateEndpointGcp{
						TargetServiceAttachment: b.Endpoint.PrivateLinkService.Gcp.TargetServiceAttachment,
					},
				}
			}
		}
		branch.Endpoints = endpoint
	}

	if b.ParentTimestamp != nil {
		branch.ParentTimestamp = timestamppb.New(*b.ParentTimestamp)
	}

	return branch, nil
}

func (b *Branch) ConvertToRpcProtoBasic() (*rpc.BasicBranch, error) {
	status := convertToRpcStatus(b.Status)
	if status == rpc.BranchStatus_BRANCH_STATE_UNSPECIFIED {
		return nil, errors.Errorf("unknown branch status: %s", b.Status)
	}

	branch := &rpc.BasicBranch{
		Id:                b.ID,
		Name:              b.Name,
		DisplayName:       b.DisplayName,
		ClusterId:         b.MainBranchID,
		ParentId:          b.ParentID,
		ParentDisplayName: b.ParentDisplayName,
		State:             status,
		CreateTime:        timestamppb.New(b.CreatedAt),
		UserPrefix:        b.UserPrefix,
		Annotations:       b.Annotations,
	}
	// updateTime
	if b.UpdateAt != nil {
		branch.UpdateTime = timestamppb.New(*b.UpdateAt)
	}
	if b.CreationFinishedTime != nil {
		branch.CreationFinishedTime = timestamppb.New(*b.CreationFinishedTime)
	}
	if b.ParentTimestamp != nil {
		branch.ParentTimestamp = timestamppb.New(*b.ParentTimestamp)
	}
	return branch, nil
}

func (b *Branch) ConvertToV1betaProtoFull() (*v1beta.Branch, error) {
	status := convertToV1betaStatus(b.Status)
	if status == v1beta.BranchState_BRANCH_STATE_UNSPECIFIED {
		return nil, errors.Errorf("unspecified branch status: %s", b.Status)
	}

	resp := &v1beta.Branch{
		Id:          b.ID,
		Name:        b.Name,
		DisplayName: b.DisplayName,
		ClusterId:   b.MainBranchID,
		ParentId:    b.ParentID,
		State:       status,
		CreateTime:  timestamppb.New(b.CreatedAt),
		Annotations: b.Annotations,
	}

	// updateTime
	if b.UpdateAt != nil {
		resp.UpdateTime = timestamppb.New(*b.UpdateAt)
	}

	// user prefix
	if b.UserPrefix != "" {
		resp.UserPrefix = b.UserPrefix
	}

	// endPoint
	if b.Endpoint != nil {
		endpoint := &v1beta.Endpoints{}
		if b.Endpoint.PublicEndpoint != nil {
			endpoint.PublicEndpoint = &v1beta.PublicEndpoint{
				Host: b.Endpoint.PublicEndpoint.Host,
				Port: b.Endpoint.PublicEndpoint.Port,
			}
		}
		if b.Endpoint.PrivateLinkService != nil {
			endpoint.PrivateEndpoint = &v1beta.PrivateEndpoint{}
			if b.Endpoint.PrivateLinkService.Aws != nil {
				endpoint.PrivateEndpoint.Host = b.Endpoint.PrivateLinkService.Host
				endpoint.PrivateEndpoint.Port = b.Endpoint.PrivateLinkService.Port
				endpoint.PrivateEndpoint.Provider = &v1beta.PrivateEndpoint_Aws{
					Aws: &v1beta.PrivateEndpointAws{
						ServiceMame:      b.Endpoint.PrivateLinkService.Aws.ServiceName,
						AvailabilityZone: b.Endpoint.PrivateLinkService.Aws.AvailabilityZones,
					},
				}
			}
			if b.Endpoint.PrivateLinkService.Gcp != nil {
				endpoint.PrivateEndpoint.Host = b.Endpoint.PrivateLinkService.Host
				endpoint.PrivateEndpoint.Port = b.Endpoint.PrivateLinkService.Port
				endpoint.PrivateEndpoint.Provider = &v1beta.PrivateEndpoint_Gcp{
					Gcp: &v1beta.PrivateEndpointGcp{
						TargetServiceAttachment: b.Endpoint.PrivateLinkService.Gcp.TargetServiceAttachment,
					},
				}
			}
		}
		resp.Endpoints = endpoint
	}

	return resp, nil
}

func (b *Branch) ConvertToV1betaProtoBasic() (*v1beta.BasicBranch, error) {
	status := convertToV1betaStatus(b.Status)
	if status == v1beta.BranchState_BRANCH_STATE_UNSPECIFIED {
		return nil, errors.Errorf("unspecified branch status: %s", b.Status)
	}

	resp := &v1beta.BasicBranch{
		Id:          b.ID,
		Name:        b.Name,
		DisplayName: b.DisplayName,
		ClusterId:   b.MainBranchID,
		ParentId:    b.ParentID,
		State:       status,
		CreateTime:  timestamppb.New(b.CreatedAt),
	}

	// updateTime
	if b.UpdateAt != nil {
		resp.UpdateTime = timestamppb.New(*b.UpdateAt)
	}

	return resp, nil
}

func convertToV1betaStatus(status constant.BranchStatus) v1beta.BranchState {
	switch status {
	case constant.BranchStatusPending, constant.BranchStatusCreating:
		return v1beta.BranchState_CREATING
	case constant.BranchStatusRunning:
		return v1beta.BranchState_READY
	case constant.BranchStatusDeleted:
		return v1beta.BranchState_DELETED
	case constant.BranchStatusRestoring:
		return v1beta.BranchState_RESTORING
	default:
		return v1beta.BranchState_BRANCH_STATE_UNSPECIFIED
	}
}

func convertToRpcStatus(status constant.BranchStatus) rpc.BranchStatus {
	switch status {
	case constant.BranchStatusPending, constant.BranchStatusCreating:
		return rpc.BranchStatus_CREATING
	case constant.BranchStatusRunning:
		return rpc.BranchStatus_ACTIVE
	case constant.BranchStatusDeleted:
		return rpc.BranchStatus_DELETED
	case constant.BranchStatusRestoring:
		return rpc.BranchStatus_RESTORING
	default:
		return rpc.BranchStatus_BRANCH_STATE_UNSPECIFIED
	}
}

func convertToConsoleStatus(status constant.BranchStatus) console.BranchState {
	switch status {
	case constant.BranchStatusPending, constant.BranchStatusCreating:
		return console.BranchState_CREATING
	case constant.BranchStatusRunning:
		return console.BranchState_READY
	case constant.BranchStatusDeleted:
		return console.BranchState_DELETED
	case constant.BranchStatusRestoring:
		return console.BranchState_RESTORING
	default:
		return console.BranchState_BRANCH_STATE_UNSPECIFIED
	}
}

func convertToV1beta1Status(status constant.BranchStatus) v1beta1.Branch_State {
	switch status {
	case constant.BranchStatusPending, constant.BranchStatusCreating:
		return v1beta1.Branch_CREATING
	case constant.BranchStatusRunning:
		return v1beta1.Branch_ACTIVE
	case constant.BranchStatusDeleted:
		return v1beta1.Branch_DELETED
	case constant.BranchStatusRestoring:
		return v1beta1.Branch_RESTORING
	default:
		return v1beta1.Branch_STATE_UNSPECIFIED
	}
}

func (b *Branch) ConvertToV1beta1ProtoBasic() (*v1beta1.Branch, error) {
	status := convertToV1beta1Status(b.Status)
	if status == v1beta1.Branch_STATE_UNSPECIFIED {
		return nil, errors.Errorf("unspecified branch status: %s", b.Status)
	}

	resp := &v1beta1.Branch{
		BranchId:          b.ID,
		Name:              b.Name,
		DisplayName:       b.DisplayName,
		ClusterId:         b.MainBranchID,
		ParentId:          b.ParentID,
		ParentDisplayName: b.ParentDisplayName,
		State:             status,
		CreateTime:        timestamppb.New(b.CreatedAt),
		CreatedBy:         b.CreatedBy,
	}

	if b.UpdateAt != nil {
		resp.UpdateTime = timestamppb.New(*b.UpdateAt)
	}

	if b.ParentTimestamp != nil {
		resp.ParentTimestamp = timestamppb.New(*b.ParentTimestamp)
	}

	if b.UserPrefix != "" {
		resp.UserPrefix = &b.UserPrefix
	}

	if b.Endpoint != nil {
		endpoint := &v1beta1.Branch_Endpoints{}
		if b.Endpoint.PublicEndpoint != nil {
			endpoint.Public = &v1beta1.Branch_Endpoints_Public{
				Host: b.Endpoint.PublicEndpoint.Host,
				Port: b.Endpoint.PublicEndpoint.Port,
				AuthorizedNetworks: ConvertAuthorizedNetworks(
					b.Endpoint.PublicEndpoint.AuthorizedNetworks,
					toV1beta1AuthorizedNetwork,
				),
			}
		}
		if b.Endpoint.PrivateLinkService != nil {
			endpoint.Private = &v1beta1.Branch_Endpoints_Private{}
			if b.Endpoint.PrivateLinkService.Aws != nil {
				endpoint.Private.Host = b.Endpoint.PrivateLinkService.Host
				endpoint.Private.Port = b.Endpoint.PrivateLinkService.Port
				endpoint.Private.Provider = &v1beta1.Branch_Endpoints_Private_Aws{
					Aws: &v1beta1.Branch_Endpoints_Private_AWS{
						ServiceName:      b.Endpoint.PrivateLinkService.Aws.ServiceName,
						AvailabilityZone: b.Endpoint.PrivateLinkService.Aws.AvailabilityZones,
					},
				}
			}
			if b.Endpoint.PrivateLinkService.Gcp != nil {
				endpoint.Private.Host = b.Endpoint.PrivateLinkService.Host
				endpoint.Private.Port = b.Endpoint.PrivateLinkService.Port
				endpoint.Private.Provider = &v1beta1.Branch_Endpoints_Private_Gcp{
					Gcp: &v1beta1.Branch_Endpoints_Private_GCP{
						ServiceAttachmentName: b.Endpoint.PrivateLinkService.Gcp.TargetServiceAttachment,
					},
				}
			}
		}
		resp.Endpoints = endpoint
	}

	if b.Annotations != nil {
		resp.Annotations = b.Annotations
	} else {
		resp.Annotations = make(map[string]string)
	}
	resp.Annotations[string(HasSetPassword)] = strconv.FormatBool(b.HasSetPwd)

	return resp, nil
}

func (b *Branch) ConvertToV1beta1ProtoFull() (*v1beta1.Branch, error) {
	return b.ConvertToV1beta1ProtoBasic()
}

func (b *Branch) GenerateBranchName() {
	b.Name = fmt.Sprintf("clusters/%s/branches/%s", b.MainBranchID, b.ID)
}

func ConvertAuthorizedNetworks[T any, U any](
	input []T,
	converter func(T) U,
) []*U {
	result := make([]*U, 0, len(input))

	for _, item := range input {
		converted := converter(item)
		result = append(result, &converted)
	}

	return result
}

func toConsoleAuthorizedNetwork(n AuthorizedNetwork) console.AuthorizedNetwork {
	return console.AuthorizedNetwork{
		DisplayName:    n.DisplayName,
		StartIpAddress: n.StartIpAddress,
		EndIpAddress:   n.EndIpAddress,
	}
}

func toRpcAuthorizedNetwork(n AuthorizedNetwork) rpc.AuthorizedNetwork {
	return rpc.AuthorizedNetwork{
		DisplayName:    n.DisplayName,
		StartIpAddress: n.StartIpAddress,
		EndIpAddress:   n.EndIpAddress,
	}
}

func toV1beta1AuthorizedNetwork(n AuthorizedNetwork) v1beta1.Branch_Endpoints_Public_AuthorizedNetwork {
	return v1beta1.Branch_Endpoints_Public_AuthorizedNetwork{
		DisplayName:    n.DisplayName,
		StartIpAddress: n.StartIpAddress,
		EndIpAddress:   n.EndIpAddress,
	}
}
