package model

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

	"gorm.io/gorm"

	"github.com/tidbcloud/tidb-management-service/utilities/clusterevent"
)

var Models = []any{
	&Cluster{}, &Provider{}, &Region{}, &RegionTenant{}, &clusterevent.EventModel{},
}

type Cluster struct {
	Model
	Versioned

	TenantId  uint64 `gorm:"index:name_tenant;not null"`
	ProjectID uint64 `gorm:"index;not null"`

	CreationFinishedAt          *time.Time `gorm:"type:TIMESTAMP NULL;creation_finished_at; default '0000-00-00 00:00:00'"`
	EncryptedCloudAdminPassword string     `gorm:"type:VARCHAR(1024);not null;default ''"`
	EncryptedRootPassword       string     `gorm:"type:VARCHAR(1024);default ''"`
	HasSetPwd                   bool       `gorm:"default:false"`

	ProviderId uint64    `gorm:"not null"`
	Provider   *Provider `gorm:"foreignkey:ProviderId"`

	RegionId uint64  `gorm:"not null"`
	Region   *Region `gorm:"foreignkey:RegionId"`

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

	Attributes ClusterAttributes `gorm:"type:JSON;not null"`

	Version string `gorm:"type:VARCHAR(255)"`

	RuVersion int32 `gorm:"not null;default:1"` // DB default 1 for existing clusters; new clusters get value from request

	EngineType string `gorm:"type:ENUM('MYSQL_COMPATIBLE','POSTGRESQL_COMPATIBLE');not null;default:'MYSQL_COMPATIBLE'"`

	ExternalInstanceID string `gorm:"type:VARCHAR(255);not null;default:''"`
}

type ExtraFields map[string]string

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

// Scan unmarshal json
func (c *ExtraFields) Scan(input any) error {
	return json.Unmarshal(input.([]byte), c)
}

type ClusterAttributes struct {
	TokenBasedAuthEnabled   bool `json:"token_based_auth_enabled"`
	WithBuiltinCDCAdminRole bool `json:"with_builtin_cdc_admin_role"`
}

// Value marshal json
func (c ClusterAttributes) Value() (driver.Value, error) {
	b, err := json.Marshal(c)
	return string(b), err
}

// Scan unmarshal json
func (c *ClusterAttributes) Scan(input any) error {
	return json.Unmarshal(input.([]byte), c)
}

type Provider struct {
	gorm.Model

	Name     string    `gorm:"index:idx_name,unique;not null;type:ENUM('aws', 'gcp', 'alicloud')"`
	Regions  []*Region `gorm:"foreignkey:ProviderId"`
	IsPublic bool      `gorm:"default:true"`
	Status   string    `gorm:"not null;type:ENUM('active', 'inactive')"`
}

type Region struct {
	gorm.Model

	Name              string           `gorm:"type:VARCHAR(255);not null"                                                                valid:"required,lowercase"`
	DisplayName       string           `gorm:"type:VARCHAR(255);not null"`
	ProviderId        uint64           `gorm:"index;not null"                                                                            valid:"required"`
	Provider          *Provider        `gorm:"foreignkey:ProviderId"`
	Status            string           `gorm:"not null;type:ENUM('active', 'inactive')"`
	IsPublic          bool             `gorm:""`
	RegionTenants     []*RegionTenant  `gorm:"foreignkey:RegionId"`
	ServiceAddress    string           `gorm:"type:VARCHAR(255);not null"`
	DefaultBackupTime string           `gorm:"default:'00:00'"`
	ServicePlan       string           `gorm:"type:varchar(32);default:'Starter';not null"                                               valid:"required,oneof=Starter Essential Premium BYOC"`
	EngineType        string           `gorm:"type:ENUM('MYSQL_COMPATIBLE','POSTGRESQL_COMPATIBLE');not null;default:'MYSQL_COMPATIBLE'"`
	Sys9Config        RegionSys9Config `gorm:"type:JSON;not null"`
}

type RegionTenant struct {
	RegionId uint64 `gorm:"not null;"`
	TenantId uint64 `gorm:"not null;"`
}

type RegionSys9Config struct {
	OemProfileRef           string `json:"oem_profile_ref"`
	AuthMode                string `json:"auth_mode"`
	TokenTtlSec             uint32 `json:"token_ttl_sec"`
	SessionRefreshBeforeSec uint32 `json:"session_refresh_before_sec"`
	RetryPolicyRef          string `json:"retry_policy_ref"`
}

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

func (c *RegionSys9Config) Scan(input any) error {
	if input == nil {
		*c = RegionSys9Config{}
		return nil
	}
	switch v := input.(type) {
	case []byte:
		return json.Unmarshal(v, c)
	case string:
		return json.Unmarshal([]byte(v), c)
	default:
		return fmt.Errorf("unsupported RegionSys9Config scan type: %T", input)
	}
}
