package schema

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

	"gorm.io/gorm"
)

type InstanceType struct {
	gorm.Model

	InstanceType string `gorm:"index:,unique"`
	ProviderId   uint64 `gorm:"index"`
	CPUNum       uint32
	MemorySizeGi uint32
	CpuArch      string `gorm:"index;type:ENUM('amd64','arm64');default:'amd64'"`

	Provider Provider // An instance type belongs to a provider.
}

type StorageTemplate struct {
	gorm.Model

	ProviderId uint64 `gorm:"index"`
	VolumeType string `gorm:"type:ENUM('gp3','io2','pd-ssd','local-storage','gp2','premiumv2','ultrassd','hyperdisk-balanced','hyperdisk-extreme')"`

	Provider Provider // A storage template belongs to a provider.
}

type ComponentTier struct {
	gorm.Model

	ComponentType string `gorm:"index:comp_name_version,unique;type:ENUM('pd','tikv','tidb','tiflash','tiproxy')"`
	Name          string `gorm:"index:comp_name_version,unique"`

	Version      string `gorm:"default:''"`
	DisplayName  string
	CPUNum       uint32
	MemorySizeGi uint32

	// Deprecated: default is calculated in mgmt on the real time.
	Default bool
	// Deprecated: use ComponentType and Name instead.
	Key string `gorm:"index:idx_comp_key,unique"`
}

type ComponentTierUnit struct {
	gorm.Model

	Key             string `gorm:"index:,unique;index:key_priority,unique"`
	ComponentType   string `gorm:"index;type:ENUM('pd','tikv','tidb','tiflash','tiproxy')"`
	ProviderId      uint64 `gorm:"index"`
	ComponentTierId uint64 `gorm:"index"`
	InstanceTypeId  uint64 `gorm:"index"`
	DefaultNodeNum  uint32

	// Data storage related fields.
	StorageTemplateId      *uint64 `gorm:"index"`
	DefaultStorageGi       uint32
	MinStorageGi           uint32 // 0 means no limit.
	MaxStorageGi           uint32 // 0 means no limit.
	DefaultIops            uint32
	MaxIops                uint32 // 0 means no limit.
	MinIops                uint32 // 0 means no limit.
	IopsPerGi              float32
	DefaultThroughPut      *uint32 // Should be not null default 0. Keep it nullable for compatibility.
	MaxThroughPut          uint32  // 0 means no limit.
	MinThroughPut          uint32  // 0 means no limit.
	ThroughputMibpsPerGi   float32
	ThroughputMibpsPerIops float32

	// Raft storage related fields.
	RaftStorageTemplateId      *uint64 `gorm:"index"`
	RaftDefaultStorageGi       uint32
	RaftMinStorageGi           uint32 // 0 means no limit.
	RaftMaxStorageGi           uint32 // 0 means no limit.
	RaftDefaultIops            uint32
	RaftMaxIops                uint32 // 0 means no limit.
	RaftMinIops                uint32 // 0 means no limit.
	RaftIopsPerGi              float32
	RaftDefaultThroughPut      uint32
	RaftMaxThroughPut          uint32 // 0 means no limit.
	RaftMinThroughPut          uint32 // 0 means no limit.
	RaftThroughputMibpsPerGi   float32
	RaftThroughputMibpsPerIops float32

	// Deprecated: priority is implicit and has a risk of being inconsistent against the mapping record order.
	Priority int32 `gorm:"index:key_priority,unique"`

	Provider            Provider            // A tier unit belongs to a provider.
	TierUnitConditions  []TierUnitCondition // A tier unit has no or many tier unit conditions.
	ComponentTier       ComponentTier       // A tier unit belongs to a tier.
	InstanceType        InstanceType        // A tier unit belongs to an instance type.
	StorageTemplate     *StorageTemplate    // A tier unit belongs to a storage template.
	RaftStorageTemplate *StorageTemplate    `gorm:"foreignKey:RaftStorageTemplateId"` // A tier unit belongs to a raft storage template.
}

// SkuSpuMapping defines the mapping between SPU and SKU for a specific tenant and project.
// It allows different tenants and projects to have different mappings.
// If no specific mapping is found for a tenant and project, the GeneralSkuSpuMapping will be used as a fallback.
// Constraints:
//   - Tenant ID cannot be 0.
//   - The SPU ID cannot be 0.
//   - The Provider ID cannot be 0.
//   - Project ID, Region ID can be 0, which means "all".
//   - The relationship between any two records (rows) is AND. For given tenant and SPU, all related records must be fetched and process to compute the final selected result.
type SkuSpuMapping struct {
	gorm.Model

	TenantId    uint64 `gorm:"index:idx_tenant_project_spu_storage_provider_region,unique"`
	ProjectId   uint64 `gorm:"index:idx_tenant_project_spu_storage_provider_region,unique"`
	SpuId       uint64 `gorm:"index:idx_tenant_project_spu_storage_provider_region,unique"`
	StorageType string `gorm:"index:idx_tenant_project_spu_storage_provider_region,unique;type:ENUM('Basic','Standard','Performance','Plus')"`
	ProviderId  uint64 `gorm:"index:idx_tenant_project_spu_storage_provider_region,unique"`
	RegionId    uint64 `gorm:"index:idx_tenant_project_spu_storage_provider_region,unique"`

	SkuId uint64 `gorm:"index"`

	// If true, this mapping cannot be used to create new instances.
	NoCreate bool `gorm:"default:false"`

	Provider Provider
	Region   Region
	Spu      ComponentTier     `gorm:"foreignKey:SpuId"`
	Sku      ComponentTierUnit `gorm:"foreignKey:SkuId"`
}

// GeneralSkuSpuMapping is similar to SkuSpuMapping, but without TenantId and ProjectId.
// It is used to define general mappings that apply to all tenants and projects.
// When both GeneralSkuSpuMapping and SkuSpuMapping exist for a specific SPU, the SkuSpuMapping takes precedence.
// Constraints:
//   - The SPU ID cannot be 0.
//   - The Provider ID cannot be 0.
//   - Region ID can be 0, which means "all".
type GeneralSkuSpuMapping struct {
	gorm.Model

	SpuId       uint64 `gorm:"index:spu_storage_provider_region,unique"`
	StorageType string `gorm:"index:spu_storage_provider_region,unique;type:ENUM('Basic','Standard','Performance','Plus')"`
	ProviderId  uint64 `gorm:"index:spu_storage_provider_region,unique"`
	RegionId    uint64 `gorm:"index:spu_storage_provider_region,unique"`

	SkuId uint64 `gorm:"index"`

	Provider Provider
	Region   Region
	Spu      ComponentTier     `gorm:"foreignKey:SpuId"`
	Sku      ComponentTierUnit `gorm:"foreignKey:SkuId"`
}

// Deprecated: Use DefaultTierUnitMapping and TenantTierUnitMapping instead.
// TierUnitCondition only contains conditions of pd component.
type TierUnitCondition struct {
	gorm.Model

	Key                 string `gorm:"index:key_tierunit,unique"`
	Operator            string `gorm:"type:ENUM('EQ','NE','GT','GE','LT','LE','IN','NOT_IN')"`
	Values              string
	ComponentTierUnitId uint64 `gorm:"index:key_tierunit,unique"`
}

// Deprecated: Use SkuSpuMapping instead.
// TierTierUnit is a temporary table to facilitate migration.
type TierTierUnit struct {
	gorm.Model

	TierUnitMapping TierUnitMapping `gorm:"type:JSON;not null"`
	ComponentTierId uint64          `gorm:"index:key_tier;unique"`

	ComponentTier ComponentTier // A tier tierunit belongs to a tier.
}

// Deprecated: Use SkuSpuMapping instead.
type TierUnitMapping struct {
	Items []*TierUnitMappingItem `json:"items"`
}

// Deprecated: Use SkuSpuMapping instead.
type TierUnitMappingItem struct {
	ProviderName string   `json:"provider_name"`
	RegionNames  []string `json:"region_names"`
	TenantIDs    []string `json:"tenant_ids"`
	SkuKey       string   `json:"tier_unit_key"`
}

func (m TierUnitMapping) Value() (driver.Value, error) {
	b, err := json.Marshal(m)
	return string(b), err
}

func (m *TierUnitMapping) Scan(input any) error {
	return json.Unmarshal(input.([]byte), m)
}
