package schema

import (
	"database/sql/driver"
	"encoding/json"
	"fmt"
	"time"

	"github.com/samber/lo"
	"gorm.io/datatypes"
	"gorm.io/gorm"

	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/entity"
)

// StringMap is a custom type for storing map[string]string as JSON in database.
// This avoids memory overhead from datatypes.JSONMap which uses map[string]any.
type StringMap map[string]string

// Scan implements sql.Scanner interface
func (m *StringMap) Scan(value any) error {
	if value == nil {
		*m = nil
		return nil
	}

	var bytes []byte
	switch v := value.(type) {
	case []byte:
		bytes = v
	case string:
		bytes = []byte(v)
	default:
		return fmt.Errorf("failed to unmarshal StringMap value: %v", value)
	}

	if len(bytes) == 0 {
		*m = nil
		return nil
	}

	return json.Unmarshal(bytes, m)
}

// Value implements driver.Valuer interface
func (m StringMap) Value() (driver.Value, error) {
	if m == nil {
		return lo.Empty[any](), nil
	}
	return json.Marshal(m)
}

// StringSlice stores []string as JSON in database.
type StringSlice []string

// Scan implements sql.Scanner interface.
func (s *StringSlice) Scan(value any) error {
	if value == nil {
		*s = nil
		return nil
	}

	var bytes []byte
	switch v := value.(type) {
	case []byte:
		bytes = v
	case string:
		bytes = []byte(v)
	default:
		return fmt.Errorf("failed to unmarshal StringSlice value: %v", value)
	}

	if len(bytes) == 0 {
		*s = nil
		return nil
	}

	return json.Unmarshal(bytes, s)
}

// Value implements driver.Valuer interface.
func (s StringSlice) Value() (driver.Value, error) {
	if s == nil {
		return lo.Empty[any](), nil
	}
	return json.Marshal(s)
}

type Cluster struct {
	gorm.Model

	// Indexes managed by migration 2026082401 :
	// - idx_clusters_deleted_created: (deleted_at, created_at DESC)
	// - idx_clusters_deploy_plan_deleted_created: (deploy_type, service_plan, deleted_at, created_at DESC)
	// - idx_clusters_org_deploy_plan_deleted: (organization_id, deploy_type, service_plan, deleted_at)
	// - idx_clusters_shadowpool_state_deleted: (shadow_pool_id, shadow_state, deleted_at)
	Name              string            `gorm:"type:VARCHAR(255);index:name,unique;not null"`
	ClusterID         uint64            `gorm:"index:cluster_id,unique;not null"`
	OrganizationID    uint64            `gorm:"index:organization_id;not null"`
	ProjectID         uint64            `gorm:"index:project_id;not null"`
	OriginalProjectID uint64            `gorm:"index:original_project_id;not null;default:0;<-:create"`
	DisplayName       string            `gorm:"type:VARCHAR(255);index:display_name;not null"`
	DeployType        entity.DeployType `gorm:"type:ENUM('DEPLOY_TYPE_DEDICATED', 'DEPLOY_TYPE_SERVERLESS', 'DEPLOY_TYPE_NEXTGEN');not null"`
	CloudProvider     string            `gorm:"type:VARCHAR(255);not null,default:'aws'"`
	Region            string            `gorm:"type:VARCHAR(255)"`
	CreatedBy         string            `gorm:"type:VARCHAR(255);not null"`
	ServicePlan       string            `gorm:"type:VARCHAR(255);default:''"`
	EngineType        string            `gorm:"type:ENUM('MYSQL_COMPATIBLE','POSTGRESQL_COMPATIBLE');not null;default:'MYSQL_COMPATIBLE'"`
	Labels            StringMap         `gorm:"type:json"`
	Annotations       StringMap         `gorm:"type:json"`

	// Shadow Pool fields for MVP
	ShadowPoolID uint64 `gorm:"index:idx_shadow;default:0"`
	ShadowState  int8   `gorm:"type:TINYINT;not null;default:0;index:idx_shadow"`
	// RuVersion DB default is 1 for existing clusters (pre-feature).
	// New clusters get ru_version from organization config (default: 2) at creation time.
	RuVersion int32 `gorm:"not null;default:1"`
}

type ResourceTag struct {
	gorm.Model

	OrganizationID uint64 `gorm:"uniqueIndex:idx_org_canonical_tag,priority:1;index:idx_org_tag_key;not null"`
	TagKey         string `gorm:"type:VARCHAR(255);index:idx_org_tag_key;not null"`
	TagValue       string `gorm:"type:VARCHAR(255);not null;default:''"`
	CanonicalTag   string `gorm:"type:VARCHAR(511);uniqueIndex:idx_org_canonical_tag,priority:2;not null"`
	CreatedBy      string `gorm:"type:VARCHAR(255);not null;default:''"`
}

type ClusterTagBinding struct {
	gorm.Model

	OrganizationID uint64 `gorm:"index:idx_org_tag_cluster,priority:1;not null"`
	ClusterID      uint64 `gorm:"uniqueIndex:idx_cluster_tag,priority:1;index:idx_org_tag_cluster,priority:3;index:idx_cluster_id;not null"`
	ResourceTagID  uint64 `gorm:"uniqueIndex:idx_cluster_tag,priority:2;index:idx_org_tag_cluster,priority:2;not null"`
	CreatedBy      string `gorm:"type:VARCHAR(255);not null;default:''"`
}

func (c *Cluster) BeforeCreate(tx *gorm.DB) (err error) {
	c.OriginalProjectID = c.ProjectID
	return nil
}

type ClusterCustomStatus struct {
	gorm.Model

	StatusID      uint64       `gorm:"index:idx_status_id,unique;not null"`
	ClusterID     uint64       `gorm:"index:idx_cluster_type_name,unique,priority:30;not null"`
	StatusType    string       `gorm:"type:VARCHAR(255);index:idx_cluster_type_name,unique,priority:20;not null"`
	StatusName    string       `gorm:"type:VARCHAR(255);index:idx_cluster_type_name,unique,priority:10;not null"`
	CustomMutex   *CustomMutex `gorm:"type:JSON"`
	TimeoutSecs   *int64       `gorm:"type:bigint unsigned;default:null"`
	OverrideState *string      `gorm:"type:VARCHAR(255);default:null"`
}

type CustomMutex struct {
	StatusType    []string `json:"status_type,omitempty"`
	ClusterAction []string `json:"cluster_action,omitempty"`
}

func (c *CustomMutex) Scan(value any) error {
	if value == nil {
		return nil
	}

	bytes, ok := value.([]byte)
	if !ok {
		return fmt.Errorf("failed to unmarshal Custom Mutex value: %v", value)
	}
	err := json.Unmarshal(bytes, c)
	return err
}

// Value implements the driver Valuer interface.
func (c CustomMutex) Value() (driver.Value, error) {
	s, err := json.Marshal(c)
	return string(s), err
}

type MaintenanceWindow struct {
	gorm.Model

	ProjectID    uint64 `gorm:"not null;index"`
	DayOfWeek    int32  `gorm:"not null"`
	HourOfDay    int32  `gorm:"not null"`
	MinuteOfHour int32  `gorm:"not null"`
}

type MaintenanceTask struct {
	gorm.Model

	ProjectID          uint64 `gorm:"not null;index"`
	Description        string
	Status             string    `gorm:"type:ENUM('pending','running','done');not null"`
	ScheduledApplyTime time.Time `gorm:"index"`
	Deadline           *time.Time
	NotificationPhase  string `gorm:"index;default:''"`
}

const (
	MaintenanceTaskNotificationPhase2WeeksAgo  string = "2_weeks_ago"
	MaintenanceTaskNotificationPhase72HoursAgo string = "72_hours_ago"
	MaintenanceTaskNotificationPhaseStarted    string = "started"
	MaintenanceTaskNotificationPhaseCompleted  string = "completed"
	MaintenanceTaskNotificationPhaseNone       string = ""
)

type MaintenanceTaskExecution struct {
	gorm.Model

	ClusterID          uint64 `gorm:"index:uniq_cluster_task,priority:1,unique;not null"`
	MaintenanceTaskID  uint64 `gorm:"index:uniq_cluster_task,priority:2,unique;not null"`
	Status             string `gorm:"type:ENUM('pending','running','done','canceled');not null"`
	CancellationReason string `gorm:"index;default:''"`
}

type MigrationCluster struct {
	gorm.Model

	SourceClusterID              uint64
	BackupID                     uint64
	RestoreMode                  string `gorm:"type:VARCHAR(64);default:''"`
	TargetProjectID              uint64
	TargetRegionID               string `gorm:"type:VARCHAR(64);default:''"`
	BackupRegionID               string `gorm:"type:VARCHAR(64);default:''"`
	TargetClusterID              uint64
	ClusterDisplayName           string           `gorm:"type:VARCHAR(255);not null"`
	ClusterTopology              *ClusterTopology `gorm:"type:JSON"`
	BrVersion                    string           `gorm:"type:VARCHAR(255);not null"`
	State                        string           `gorm:"column:state;type:ENUM('PENDING','RUNNING','SUCCEEDED','FAILED');index:idx_state"`
	TargetClusterVersion         string           `gorm:"type:VARCHAR(255);default:''"`
	BrImageTag                   string           `gorm:"type:VARCHAR(255);default:''"`
	BrCustomOptions              string           `gorm:"type:TEXT"`
	TidbImageTag                 string           `gorm:"type:VARCHAR(255);default:''"`
	TikvImageTag                 string           `gorm:"type:VARCHAR(255);default:''"`
	PdImageTag                   string           `gorm:"type:VARCHAR(255);default:''"`
	TiflashImageTag              string           `gorm:"type:VARCHAR(255);default:''"`
	CreateClusterTidbImageTag    string           `gorm:"type:VARCHAR(255);default:''"`
	CreateClusterTikvImageTag    string           `gorm:"type:VARCHAR(255);default:''"`
	CreateClusterPdImageTag      string           `gorm:"type:VARCHAR(255);default:''"`
	CreateClusterTiflashImageTag string           `gorm:"type:VARCHAR(255);default:''"`
}

type DataSynchronization struct {
	gorm.Model

	SourceClusterID uint64 `gorm:"not null;index:idx_source_cluster_id"`
	TargetClusterID uint64 `gorm:"not null;index:idx_target_cluster_id"`
	// TargetTidbGroupID is the target TiDB node group id within the target cluster; 0 means default group.
	TargetTidbGroupID uint64 `gorm:"not null;default:0"`
	Name              string `gorm:"type:VARCHAR(255);not null"`
	Description       string `gorm:"type:TEXT"`
	Status            string `gorm:"type:VARCHAR(64);not null;default:'PENDING'"`
	Rcu               uint64 `gorm:"not null;default:256"`

	// Dataflow changefeed connector id; 0 means not created yet.
	ChangefeedConnectorID uint64 `gorm:"not null;default:0"`

	// Dataflow sink endpoint id for privatelink between source/target (cross-project); 0 means not created.
	SinkEndpointID uint64 `gorm:"not null;default:0"`

	// Available zones for source cluster
	AvailableZoneIDs StringSlice `gorm:"type:JSON"`

	StartTso *string `gorm:"type:VARCHAR(255)"`
}

type ClusterTopology struct {
	TidbNodeSetting    *TidbNodeSetting    `json:"tidb_node_setting"`
	TikvNodeSetting    *StorageNodeSetting `json:"tikv_node_setting"`
	TiflashNodeSetting *StorageNodeSetting `json:"tiflash_node_setting"`
}

type TidbNodeGroup struct {
	DisplayName    string `json:"display_name"`
	IsDefaultGroup bool   `json:"is_default_group"`
	NodeCount      *int32 `json:"node_count"`
}

type TidbNodeSetting struct {
	NodeSpecKey    string          `json:"node_spec_key"`
	TidbNodeGroups []TidbNodeGroup `json:"tidb_node_groups"`
}

type StorageNodeSetting struct {
	NodeSpecKey string `json:"node_spec_key"`
	NodeCount   *int32 `json:"node_count"`
}

func (c *ClusterTopology) Scan(value any) error {
	if value == nil {
		return nil
	}

	bytes, ok := value.([]byte)
	if !ok {
		return fmt.Errorf("failed to unmarshal Cluster Topology value: %v", value)
	}
	err := json.Unmarshal(bytes, c)
	return err
}

// Value implements the driver Valuer interface.
func (c ClusterTopology) Value() (driver.Value, error) {
	s, err := json.Marshal(c)
	return string(s), err
}

// ShadowPool stores configuration for shadow pools
type ShadowPool struct {
	ID uint64 `gorm:"primaryKey;not null"`

	CreatedAt time.Time
	UpdatedAt time.Time
	DeletedAt gorm.DeletedAt `gorm:"index"`

	Name string `gorm:"type:VARCHAR(255);not null"`

	OrganizationID uint64 `gorm:"uniqueKey:idx_pool_key,priority:1;not null"`
	ProjectID      uint64 `gorm:"uniqueKey:idx_pool_key,priority:2;not null"`
	Region         string `gorm:"type:VARCHAR(64);uniqueKey:idx_pool_key,priority:3;not null"`
	CloudProvider  string `gorm:"type:VARCHAR(32);uniqueKey:idx_pool_key,priority:4;not null"`
	ServicePlan    string `gorm:"type:VARCHAR(32);uniqueKey:idx_pool_key,priority:5;not null"`

	Priority uint64 `gorm:"not null;default:0"`    // Pool selection priority, lower value means higher priority
	Enabled  bool   `gorm:"not null;default:true"` // Whether the pool is active

	MinCapacity int `gorm:"not null;default:0"` // Minimum number of prepared instances
	MaxCapacity int `gorm:"not null;default:0"` // Maximum number of prepared instances

	AutoScalingConfig datatypes.JSON `gorm:"type:JSON;"` // Auto-scaling strategy configuration
	InstanceDemand    datatypes.JSON `gorm:"type:JSON;"`
	InitSQLs          datatypes.JSON `gorm:"type:JSON;"` // SQL statements to execute during cluster initialization
}

// ActivationCode is only used for the MVP phase of TiDB Cloud Zero. It's deprecated and will be removed in the future. Do not use it for any new features.
type ActivationCode struct {
	gorm.Model

	Purpose       string    `gorm:"type:VARCHAR(255);default:''"`
	DatabaseQuota int32     `gorm:"not null;default:100"`
	UsedQuota     int32     `gorm:"not null;default:0"`
	ExpiresAt     time.Time `gorm:"not null;index"`
}

// ZeroRentRecord stores the mapping between a cluster ID and its encrypted secret
// generated when the cluster is rented from a shadow pool. ClusterID is a unique key
// so that each cluster has at most one active secret at any time.
type ZeroRentRecord struct {
	gorm.Model

	ClusterID       uint64 `gorm:"index:idx_cluster_id,unique;not null"`
	EncryptedSecret string `gorm:"type:TEXT;not null"`
	SourceIP        string `gorm:"type:VARCHAR(64);not null;default:''"`
	Tag             string `gorm:"type:VARCHAR(256);not null;default:''"`
}

// OrganizationRuConfig stores per-organization default RU version configuration.
// When a new cluster is created, the system looks up this table to determine the default ru_version.
// If no record exists for the organization, the system default (2) is used.
type OrganizationRuConfig struct {
	gorm.Model

	OrganizationID   uint64 `gorm:"uniqueIndex;not null"`
	DefaultRuVersion int32  `gorm:"not null;default:2"`
}

// ProjectMigrationItem holds the source/target project mapping for a single migration step.
type ProjectMigrationItem struct {
	SourceProjectID   uint64 `json:"source_project_id"`
	TargetProjectID   uint64 `json:"target_project_id"`
	TargetProjectName string `json:"target_project_name"`
}

// ProjectMigrationItems is a JSON-serialisable slice of ProjectMigrationItem.
type ProjectMigrationItems []ProjectMigrationItem

func (p *ProjectMigrationItems) Scan(value any) error {
	if value == nil {
		*p = nil
		return nil
	}
	var b []byte
	switch v := value.(type) {
	case []byte:
		b = v
	case string:
		b = []byte(v)
	default:
		return fmt.Errorf("failed to unmarshal ProjectMigrationItems: %v", value)
	}
	if len(b) == 0 {
		*p = nil
		return nil
	}
	return json.Unmarshal(b, p)
}

func (p ProjectMigrationItems) Value() (driver.Value, error) {
	if p == nil {
		return nil, nil //nolint:nilnil // nil driver.Value means SQL NULL; this is standard driver.Valuer behavior
	}
	return json.Marshal(p)
}

// ProjectMigrationTask records the org-level project-migration batch task.
// There is at most one record per org (unique index on org_id).
type ProjectMigrationTask struct {
	gorm.Model

	OrgID      uint64                `gorm:"uniqueIndex;not null"`
	State      string                `gorm:"type:ENUM('PENDING','RUNNING','SUCCEED','FAILED');not null;default:'PENDING'"`
	Migrations ProjectMigrationItems `gorm:"type:JSON"`
}

type DedicatedToPremiumPrecheck struct {
	gorm.Model

	SourceClusterID        uint64  `gorm:"not null;index"`
	MigrationAccountEmail  string  `gorm:"type:VARCHAR(320);not null"`
	TargetPremiumProjectID *uint64 `gorm:"index"`
	Applicant              string  `gorm:"type:VARCHAR(320);not null"`
	State                  string  `gorm:"type:ENUM('PENDING','RUNNING','SUCCEEDED','FAILED');not null;default:'PENDING';index"`
	FailureMessage         string  `gorm:"type:TEXT"`
	StartedAt              *time.Time
	FinishedAt             *time.Time
}

type DedicatedToPremiumPrecheckItem struct {
	gorm.Model

	PrecheckID     uint64 `gorm:"not null;uniqueIndex:uniq_precheck_category_key,priority:1;index"`
	Category       string `gorm:"type:VARCHAR(64);not null;uniqueIndex:uniq_precheck_category_key,priority:2"`
	CheckKey       string `gorm:"type:VARCHAR(128);not null;uniqueIndex:uniq_precheck_category_key,priority:3"`
	Title          string `gorm:"type:VARCHAR(255);not null"`
	Position       int    `gorm:"not null"`
	Status         string `gorm:"type:VARCHAR(32);not null;default:''"`
	Why            string `gorm:"type:TEXT"`
	Evidence       string `gorm:"type:TEXT"`
	Action         string `gorm:"type:TEXT"`
	ExecutionState string `gorm:"type:ENUM('PENDING','RUNNING','COMPLETED','FAILED');not null;default:'PENDING'"`
	ExecutionError string `gorm:"type:TEXT"`
}

// FsRegion stores the fs regional server endpoint for a logical region.
// The fs system has no global service; this table is the source of truth for
// the fs region OpenAPI served by the mgmt service.
type FsRegion struct {
	gorm.Model

	// CloudProvider is the lowercase cloud provider name, e.g. "aws".
	CloudProvider string `gorm:"type:VARCHAR(32);not null;uniqueIndex:uk_fs_regions_provider_region,priority:1"`
	// Region is the region code, e.g. "us-west-2".
	Region         string `gorm:"type:VARCHAR(64);not null;uniqueIndex:uk_fs_regions_provider_region,priority:2"`
	DisplayName    string `gorm:"type:VARCHAR(255);not null;default:''"`
	ServiceAddress string `gorm:"type:VARCHAR(255);not null;default:''"`
	// Status is the lifecycle state of the region: ACTIVE or INACTIVE.
	Status string `gorm:"type:VARCHAR(32);not null;default:'ACTIVE'"`
}
