package cluster

import (
	"database/sql"
	"time"

	"google.golang.org/protobuf/types/known/timestamppb"
	"gorm.io/gorm"

	globalcommonv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/common/v1"
	globalngv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/nextgen/v1"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/entity"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/infra/repoimpl/schema"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
	"github.com/tidbcloud/tidb-management-service/utilities/enum"
)

func ConvertClusterEntityToModel(
	en *entity.Cluster,
	providerId uint64,
) (*schema.Cluster, bizerr.BizError) {
	if en == nil {
		return nil, bizerr.New(bizerr.ErrCodeParams, "cluster entity is nil")
	}
	if en.RegionId == 0 {
		return nil, bizerr.New(bizerr.ErrCodeParams, "region id is not set for cluster entity")
	}

	m := &schema.Cluster{
		ID:                                en.ClusterId,
		TenantId:                          en.OrganizationId,
		ProjectId:                         en.ProjectId,
		ProviderId:                        providerId,
		RegionId:                          en.RegionId,
		State:                             en.State.String(),
		EncryptedCloudAdminPassword:       en.EncryptedCloudAdminPassword,
		EncryptedPlatformAdminPassword:    en.EncryptedPlatformAdminPassword,
		EncryptedMaintenanceAdminPassword: en.EncryptedMaintenanceAdminPassword,
		HasSetPwd:                         en.HasRootPassword,
		Versioned: schema.Versioned{
			Versioning: en.Versioning,
		},
		ServicePlan:          en.ServicePlan.String(),
		HighAvailabilityType: en.HighAvailabilityType.String(),
		Attributes: schema.ClusterAttributes{
			TokenBasedAuthEnabled: en.TokenBasedAuthEnabled,
		},
		Version:                    en.Version,
		SharedPoolId:               en.SharedPoolId,
		ReservedCapacityTemplateId: en.ReservedCapacityTemplateId,
		MaxRcu:                     en.MaxRcu,
		MinRcu:                     en.MinRcu,
		CapacityUpdatedAt:          en.CapacityUpdatedAt,
		PlanReplayerExpireAt:       en.PlanReplayerExpireAt,
		EncryptionKeyId:            en.EncryptionKeyId,
		RuVersion:                  en.RuVersion,
	}

	// full time
	if en.CreateTime.IsValid() {
		m.CreatedAt = en.CreateTime.AsTime()
	}

	if en.UpdateTime.IsValid() {
		m.UpdatedAt = en.UpdateTime.AsTime()
	}

	if en.DeleteTime.IsValid() {
		m.DeletedAt = gorm.DeletedAt{
			Valid: true,
			Time:  en.DeleteTime.AsTime(),
		}
	}

	if en.CreateFinishedTime.IsValid() {
		m.CreateFinishedAt = sql.NullTime{
			Valid: true,
			Time:  en.CreateFinishedTime.AsTime(),
		}
	}

	return m, nil
}

func convertClusterModelToEntity(
	m *schema.Cluster,
	dataplaneId string,
) (*entity.Cluster, bizerr.BizError) {
	if m == nil {
		return nil, bizerr.New(bizerr.ErrCodeParams, "cluster model is nil")
	}
	if m.Region == nil {
		return nil, bizerr.New(bizerr.ErrCodeParams, "region is not set for cluster model")
	}
	if m.Provider == nil {
		return nil, bizerr.New(bizerr.ErrCodeParams, "provider is not set for cluster model")
	}

	en := &entity.Cluster{
		Cluster: &globalngv1.Cluster{
			ClusterId:             m.ID,
			OrganizationId:        m.TenantId,
			CreateTime:            timestamppb.New(m.CreatedAt),
			UpdateTime:            timestamppb.New(m.UpdatedAt),
			State:                 globalngv1.Cluster_State(globalngv1.Cluster_State_value[m.State]),
			HasRootPassword:       m.HasSetPwd,
			TokenBasedAuthEnabled: m.Attributes.TokenBasedAuthEnabled,
			ServicePlan:           globalcommonv1.ServicePlan(globalcommonv1.ServicePlan_value[m.ServicePlan]),
			HighAvailabilityType: globalngv1.Cluster_HighAvailabilityType(
				globalngv1.Cluster_HighAvailabilityType_value[m.HighAvailabilityType],
			),
			RegionDisplayName:                 m.Region.DisplayName,
			EncryptedPlatformAdminPassword:    m.EncryptedPlatformAdminPassword,
			EncryptedMaintenanceAdminPassword: m.EncryptedMaintenanceAdminPassword,
			RegionLogicalName:                 m.Region.Name,
			Version:                           m.Version,
			SharedPoolId:                      m.SharedPoolId,
			ReservedCapacityTemplateId:        m.ReservedCapacityTemplateId,
			DataplaneId:                       dataplaneId,
			MaxRcu:                            m.MaxRcu,
			MinRcu:                            m.MinRcu,
			RuVersion:                         m.RuVersion,
			PlanReplayerStatus:                cvtPlanReplayerStatus(m.ID, m.PlanReplayerExpireAt),
		},
		ProjectId:                   m.ProjectId,
		RegionId:                    uint64(m.Region.ID),
		CloudProviderId:             uint64(m.Provider.ID),
		Versioning:                  m.Versioning,
		EncryptionKeyId:             m.EncryptionKeyId,
		CapacityUpdatedAt:           m.CapacityUpdatedAt,
		PlanReplayerExpireAt:        m.PlanReplayerExpireAt,
		EncryptedCloudAdminPassword: m.EncryptedCloudAdminPassword,
	}
	if m.DeletedAt.Valid {
		en.DeleteTime = timestamppb.New(m.DeletedAt.Time)
	} else {
		en.DeleteTime = nil
	}
	if m.CreateFinishedAt.Valid {
		en.CreateFinishedTime = timestamppb.New(m.CreateFinishedAt.Time)
	} else {
		en.CreateFinishedTime = nil
	}
	if m.CapacityUpdatedAt != nil {
		en.Cluster.CapacityUpdatedAt = timestamppb.New(*m.CapacityUpdatedAt)
	}
	provider, err := enum.FromString[globalcommonv1.CloudProvider_Name](m.Provider.Name)
	if err != nil {
		return nil, bizerr.WrapError(err, "convert cloud provider")
	}
	en.Cluster.CloudProviderId = globalcommonv1.CloudProvider_Name(provider)

	region, err := enum.RegionFromString(m.Provider.Name, m.Region.RegionName)
	if err != nil || region == globalcommonv1.Region_NAME_UNSPECIFIED {
		return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "invalid region: %s", m.Region.RegionName)
	}
	en.Cluster.RegionId = region

	return en, nil
}

func cvtPlanReplayerStatus(clusterID uint64, expireAt *time.Time) *globalngv1.PlanReplayerStatus {
	status := &globalngv1.PlanReplayerStatus{ClusterId: clusterID}
	if expireAt == nil {
		status.Enabled = false
		return status
	}

	if expireAt.After(time.Now().UTC()) {
		status.Enabled = true
		status.ExpireTime = timestamppb.New(expireAt.UTC())
		return status
	}

	status.Enabled = false
	return status
}
