//nolint:lll
package entity

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

//go:generate ../../../tools/bin/goqueryset -in entity.go

//gen:qs
type (
	BillingRecord struct {
		Base
		TenantID    ID                      `gorm:"type:BIGINT unsigned;not null"`
		ClusterID   ID                      `gorm:"type:BIGINT unsigned;not null;index:idx_c_c_m,priority:1"`
		MeasuredTS  time.Time               `gorm:"type:TIMESTAMP;measured_ts;not null;index:idx_c_c_m,priority:3"`
		SKU         string                  `gorm:"type:VARCHAR(1024);not null"`
		Total       int64                   `gorm:"type:BIGINT;not null"`
		ExtraLabels BillingRecordExtraLabel `gorm:"type:VARCHAR(1024)"`
		Category    string                  `gorm:"type:VARCHAR(128);not null;index:idx_c_c_m,priority:2"`
		Synced      bool                    `gorm:"not null;default:false"`
		CostID      string                  `gorm:"cost_id;type:VARCHAR(64);not null"`
	}
)

type BillingRecordExtraLabel map[string]string

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

func (labels *BillingRecordExtraLabel) Scan(input interface{}) error {
	return json.Unmarshal(input.([]byte), labels)
}

// gen:qs
type (
	ChangefeedSpecification struct {
		ID       ID
		Name     string `gorm:"uniqueIndex:idx_type_business_type;type:VARCHAR(64);not null"`
		RCUNum   uint32
		RPSLimit uint32
	}
	CDCInstanceType struct {
		ID                ID
		SpecificationCode string `gorm:"type:VARCHAR(64);uniqueIndex:unique_name;not null"`
		RCUNum            uint32 `gorm:"uniqueIndex:unique_rcu;not null"`
		CpuNum            uint32
		MemoryGi          uint32
		Replicas          uint32
	}

	CDCMachineType struct {
		ID                  ID
		CpuNum              uint32 `gorm:"uniqueIndex:unique_cpu_mem,priority:1;not null"`
		MemoryGi            uint32 `gorm:"uniqueIndex:unique_cpu_mem,priority:2;not null"`
		StorageGi           uint32
		AWSStorageType      string `gorm:"type:VARCHAR(64)"`
		GCPStorageType      string `gorm:"type:VARCHAR(64)"`
		AzureStorageType    string `gorm:"type:VARCHAR(64)"`
		AliCloudStorageType string `gorm:"type:VARCHAR(64)"`
		Iops                uint32
		ThroughputMbs       uint32
	}

	TiCDCCustomStorage struct {
		Updatable
		TiCDCClusterID ID
		SizeGi         uint32
		ThroughputMbs  uint32
		Iops           uint32
	}
)

// gen:qs
type (
	BizChangefeed struct {
		Versioned
		SoftDelete
		ID                ID                              `gorm:"primary_key;not null;autoIncrement:false"`
		CreatedAt         time.Time                       `gorm:"created_at;type:TIMESTAMP;not null;<-:create"` // create and read only
		UpdatedAt         *time.Time                      `gorm:"updated_at;type:TIMESTAMP"`
		Name              string                          `gorm:"type:VARCHAR(128)"`
		ClusterID         ID                              `gorm:"type:bigint(20) unsigned;not null;index"`
		SharedPoolID      string                          `gorm:"type:VARCHAR(255)"`
		TenantID          ID                              `gorm:"type:bigint(20) unsigned;not null;index"`
		TiCDCClusterID    ID                              `gorm:"type:bigint(20) unsigned"`
		DownstreamType    TiCDCChangefeedDownStreamType   `gorm:"type:ENUM('mysql', 'kafka', 'serverless_tidb', 's3', 'gcs', 'pulsar');not null"`
		DownstreamParams  string                          `gorm:"type:MEDIUMTEXT"`
		SpecificationCode string                          `gorm:"type:VARCHAR(255);not null"`
		NetworkType       ChangefeedDownstreamNetworkType `gorm:"type:ENUM('unknown', 'internet','vpc_peering', 'private_link');not null"`
		NetworkInfo       string                          `gorm:"type:MEDIUMTEXT"`
		OperationStatus   BizChangefeedOperationStatus    `gorm:"type:ENUM('creating', 'normal', 'scaling', 'paused', 'deleting', 'deleted', 'failed');not null"`
		CreatorID         ID                              `gorm:"type:bigint(20) unsigned;not null"`
		ErrMsg            string                          `gorm:"type:TEXT"`
		BizPLEndpointID   ID                              `gorm:"type:bigint(20) unsigned;default:0;"`
	}
)

func (c *BizChangefeed) GetID() ID {
	return c.ID
}

func (c *BizChangefeed) GenKernelChangefeedID() string {
	return fmt.Sprintf("changefeed-%d", c.ID)
}

// gen:qs
type (
	// DataPipeline is the durable orchestration record for one source TiDB
	// cluster. Child Export and Changefeed records remain owned by their
	// existing tables; this row stores only their identifiers and the immutable
	// plan needed to adopt them after a retry.
	DataPipeline struct {
		Versioned
		SoftDelete
		ID        ID         `gorm:"primary_key;not null;autoIncrement:false"`
		CreatedAt time.Time  `gorm:"created_at;type:TIMESTAMP;not null;<-:create"`
		UpdatedAt *time.Time `gorm:"updated_at;type:TIMESTAMP"`

		TenantID  ID `gorm:"type:bigint(20) unsigned;not null;index:idx_data_pipeline_scope,priority:1"`
		ClusterID ID `gorm:"type:bigint(20) unsigned;not null;index:idx_data_pipeline_scope,priority:2"`
		CreatorID ID `gorm:"type:bigint(20) unsigned;not null"`
		// Creator is the email the row was created under, captured at create
		// time rather than resolved on read: the account client can look a user
		// up by token but not by id, so the identity has to be recorded while
		// the caller is still in hand. Rows written before this column existed
		// carry only CreatorID.
		Creator string `gorm:"type:varchar(255);not null;default:''"`
		Name    string `gorm:"type:varchar(128);not null"`
		// The console lists by scope; the observation worker pages by status
		// across every tenant, so that query needs its own leading column.
		Status DataPipelineStatus `gorm:"type:varchar(32);not null;index:idx_data_pipeline_scope,priority:3;index:idx_data_pipeline_status"`
		// StatusEnteredAt is when Status last changed. UpdatedAt cannot answer
		// that: the observation worker saves every polled row on every pass, so
		// UpdatedAt is "when we last looked", which for a stuck Pipeline is
		// always a few seconds ago. Anything asking how long a Pipeline has
		// been in its current state has to read this instead.
		StatusEnteredAt *time.Time `gorm:"type:TIMESTAMP"`
		// ChangefeedSpecification is top-level because it is a primary editable
		// capacity control and a common List/Get filter/display field.
		ChangefeedSpecification string `gorm:"type:varchar(255);not null"`

		Params        DataPipelineParams        `gorm:"type:JSON;not null;serializer:json"`
		ExecutionPlan DataPipelineExecutionPlan `gorm:"type:JSON;not null;serializer:json"`
		IntervalPlan  DeliveryIntervalPlan      `gorm:"type:JSON;not null;serializer:json"`

		SnapshotTSO uint64 `gorm:"type:bigint(20) unsigned;not null"`
		// StagingRoot is immutable. Its Pipeline-ID component guarantees that a
		// recreated Pipeline cannot reuse a partially written prefix.
		StagingRoot string                     `gorm:"type:varchar(1024);not null"`
		Children    DataPipelineChildResources `gorm:"type:JSON;not null;serializer:json"`
		Observation DataPipelineObservation    `gorm:"type:JSON;not null;serializer:json"`

		// LastError is the terminal or in-flight failure shown on the row. It
		// stays JSON because it is a whole optional record, not a field.
		LastError *DataPipelineError `gorm:"type:JSON;serializer:json"`
	}
)

func (p *DataPipeline) GetID() ID {
	return p.ID
}

// gen:qs
type (
	TiCDCCluster struct {
		Updatable
		Versioned

		CloudProvider CloudProviderType `gorm:"type:ENUM('aws', 'gcp', 'azure','alicloud');not null"`

		SharedPoolID string `gorm:"type:VARCHAR(255)"` // up stream cluster id

		Name              string                    `gorm:"type:VARCHAR(255);uniqueIndex:unique_name_deleted_time,priority:1;not null;"` // kernel cluster id
		DeletedAt         *time.Time                `gorm:"type:TIMESTAMP;deleted_at"`
		DeletedTime       int64                     `gorm:"type:bigint(20);not null;default:0;uniqueIndex:unique_name_deleted_time,priority:2"`
		Version           string                    `gorm:"type:VARCHAR(255)"`
		Region            string                    `gorm:"type:VARCHAR(255)"`
		SpecificationCode string                    `gorm:"type:VARCHAR(255)"`
		Status            TiCDCClusterStatus        `gorm:"type:ENUM('creating', 'running', 'deleting', 'deleted', 'scaling', 'upgrading');not null;"`
		Action            TiCDCClusterSettingAction `gorm:"type:JSON;serializer:json"`
	}

	TiCDCBizChangefeedResource struct {
		Updatable
		Versioned

		TiCDCClusterID        ID     `gorm:"type:bigint(20) unsigned;not null;uniqueIndex:unique_cdc_changefeed_id,priority:1"`
		BizChangefeedID       ID     `gorm:"type:bigint(20) unsigned;not null;uniqueIndex:unique_cdc_changefeed_id,priority:2"`
		ExpectedSpecification string `gorm:"type:VARCHAR(255)"`
		ActualSpecification   string `gorm:"type:VARCHAR(255)"`
	}

	TiCDCChangefeed struct {
		Updatable
		Versioned

		KernelChangefeedID string     `gorm:"type:VARCHAR(255);not null;default:'';uniqueIndex:uniq_kernel_id"`
		DeletedAt          *time.Time `gorm:"type:TIMESTAMP;deleted_at"`

		TenantID          ID            `gorm:"type:bigint(20) unsigned;not null;index;"`
		TiCDCClusterID    ID            `gorm:"type:bigint(20) unsigned;not null;index;"`
		TiCDCCluster      *TiCDCCluster `gorm:"foreignkey:TiCDCClusterID"`
		UpStreamClusterID ID            `gorm:"type:bigint(20) unsigned;not null;index;"`
		BizChangefeedID   ID            `gorm:"type:bigint(20) unsigned;not null;default:0;index:idx_biz_type_id,priority:2"`

		DownStreamType TiCDCChangefeedDownStreamType `gorm:"type:ENUM('mysql', 'kafka', 'serverless_tidb', 's3', 'gcs', 'pulsar');not null"`
		KernelMetrics  TiCDCChangefeedKernelMetrics  `gorm:"type:JSON;serializer:json"`

		// KernelStatus tracks whether the kernel-side changefeed exists.
		// Default 'active' keeps existing rows backwards-compatible without
		// backfill; the create DAG explicitly writes 'pending' before calling
		// regional and flips to 'active' once the kernel ack is observed.
		KernelStatus TiCDCChangefeedKernelStatus `gorm:"type:ENUM('pending','active');not null;default:'active'"`
	}
)

type TiCDCChangefeedKernelMetrics struct {
	ID            string                             `json:"id,omitempty"`
	FeedState     ChangefeedKernelStatus             `json:"state,omitempty"`
	CheckpointTSO uint64                             `json:"checkpoint_tso,omitempty"`
	RunningError  *TiCDCChangefeedKernelRunningError `json:"error,omitempty"`
	UpdatedAt     *time.Time                         `json:"updated_at,omitempty"`
}

type TiCDCChangefeedKernelRunningError struct {
	Addr    string `json:"addr,omitempty"`
	Code    string `json:"code,omitempty"`
	Message string `json:"message,omitempty"`
}

func (e *TiCDCChangefeedKernelRunningError) Error() string {
	return fmt.Sprintf("code: %s, message: %s, ticdc addr: %s", e.Code, e.Message, e.Addr)
}

// gen:qs
type (
	KernelChangefeedTrafficRecord struct {
		Updatable
		TenantID      ID                         `gorm:"type:BIGINT unsigned;not null"`
		ClusterID     ID                         `gorm:"type:BIGINT unsigned;not null;index:idx_cluster,priority:1"`
		ChangefeedID  ID                         `gorm:"index;not null"`
		CloudProvider CloudProviderType          `gorm:"type:ENUM('aws', 'gcp','azure','alicloud');not null"`
		Region        string                     `gorm:"type:VARCHAR(255)"`
		StartTS       *time.Time                 `gorm:"type:timestamp;"`
		EndTS         *time.Time                 `gorm:"type:timestamp;"`
		Traffic       int64                      `gorm:"not null;default:0;"`
		Extension     ChangefeedTrafficExtension `gorm:"type:VARCHAR(1024)"`
		Synced        bool                       `gorm:"not null;default:false"`
	}
)

// gen:qs
type (
	BizSinkEndpoint struct {
		Updatable
		Versioned
		TenantID           ID                         `gorm:"type:bigint(20) unsigned;not null;index:idx_tid_sid_phase,priority:1;"`
		SharedPoolID       string                     `gorm:"type:bigint(20) unsigned;not null;index:idx_tid_sid_phase,priority:2;"`
		CloudProvider      CloudProviderType          `gorm:"type:ENUM('aws', 'gcp','azure','alicloud');not null"`
		Region             string                     `gorm:"type:varchar(255);not null;"`
		Zones              AZs                        `gorm:"type:JSON;serializer:json"`
		Name               string                     `gorm:"type:varchar(255);not null;"`
		ServiceName        string                     `gorm:"type:varchar(255);not null;"`
		ServiceRegion      string                     `gorm:"type:varchar(64);not null;"`
		Status             BizSinkEndpointStatus      `gorm:"type:enum('creating', 'pending-acceptance', 'available', 'rejected', 'failed', 'deleting', 'deleted');not null;"`
		ErrMsg             string                     `gorm:"type:text;"`
		RegionalEndpointID string                     `gorm:"type:varchar(255);not null;"`
		CloudEndpointID    string                     `gorm:"type:varchar(255);not null;"`
		CloudAccountID     string                     `gorm:"type:varchar(255);not null;"`
		DeletedTime        int64                      `gorm:"type:bigint(20);not null;default:0;"`
		IsCustomDomain     bool                       `gorm:"default:false"`
		RootDomainName     string                     `gorm:"type:varchar(1024);not null;"`
		DNSRecords         []BizSinkEndpointDNSRecord `gorm:"type:JSON;serializer:json"`
		EndpointAddress    string                     `gorm:"type:varchar(255);not null;"`
		// EndpointType distinguishes the private-link model. Empty defaults to
		// BizSinkEndpointTypeVPCEndpointService for backward compatibility.
		EndpointType BizSinkEndpointType `gorm:"type:varchar(32);not null;default:'vpc_endpoint_service'"`
		// MSKArn / AuthType / BootstrapBroker are only used when
		// EndpointType == BizSinkEndpointTypeAmazonMSK.
		MSKArn          string                  `gorm:"type:varchar(512);not null;default:''"`
		AuthType        BizSinkEndpointAuthType `gorm:"type:varchar(32);not null;default:''"`
		BootstrapBroker string                  `gorm:"type:text"`
		// Event attribution is persisted so asynchronous terminal events use the
		// same cluster, project, and actor as the API transition that initiated them.
		EventClusterID ID     `gorm:"type:bigint(20) unsigned;not null;default:0"`
		EventProjectID ID     `gorm:"type:bigint(20) unsigned;not null;default:0"`
		EventAuthorID  uint64 `gorm:"type:bigint(20) unsigned;not null;default:0"`
		EventAuthor    string `gorm:"type:varchar(255);not null;default:''"`
	}

	BizSinkEndpointUsage struct {
		Base
		SoftDelete
		BizSinkEndpointID ID `gorm:"type:bigint(20) unsigned;not null;uniqueIndex:uniq_endpoint_owner_type,priority:1;"`
		// Deprecated: BizChangefeedID is kept for backward compatibility.
		// Use OwnerID instead. This column will be dropped in a future migration.
		BizChangefeedID ID `gorm:"type:bigint(20) unsigned;not null;default:0;"`
		// OwnerID stores the referencing resource ID as a string.
		// - For UsageType="changefeed": this is the numeric BizChangefeed.ID as a string.
		//   Callers should parse it to uint64 when joining with the biz_changefeeds table.
		// - For UsageType="premium_dm_task": this is the PremiumDMTask string ID (e.g. "dmtsk-xxx").
		// - For UsageType="premium_dm_precheck": this is the PremiumDMPrecheck string ID (e.g. "dmpck-xxx").
		OwnerID   string                   `gorm:"type:varchar(255);not null;uniqueIndex:uniq_endpoint_owner_type,priority:2;"`
		UsageType BizSinkEndpointUsageType `gorm:"type:varchar(64);not null;default:'changefeed';uniqueIndex:uniq_endpoint_owner_type,priority:3;"`
	}
)

type (
	BizSinkEndpointStatus    string
	BizSinkEndpointUsageType string
	BizSinkEndpointType      string
	BizSinkEndpointAuthType  string
	AZs                      []string
)

const (
	BizSinkEndpointTypeVPCEndpointService BizSinkEndpointType = "vpc_endpoint_service"
	BizSinkEndpointTypeAmazonMSK          BizSinkEndpointType = "amazon_msk"

	// BizSinkEndpointAuthSASLScram is the only MSK auth scheme implemented for now.
	BizSinkEndpointAuthSASLScram BizSinkEndpointAuthType = "sasl_scram"
	BizSinkEndpointAuthSASLIAM   BizSinkEndpointAuthType = "sasl_iam"
)

const (
	BizPLEndpointStatusCreating          BizSinkEndpointStatus = "creating"
	BizPLEndpointStatusPendingAcceptance BizSinkEndpointStatus = "pending-acceptance"
	BizPLEndpointStatusAvailable         BizSinkEndpointStatus = "available"
	BizPLEndpointStatusRejected          BizSinkEndpointStatus = "rejected"
	BizPLEndpointStatusFailed            BizSinkEndpointStatus = "failed"
	BizPLEndpointStatusDeleting          BizSinkEndpointStatus = "deleting"
	BizPLEndpointStatusDeleted           BizSinkEndpointStatus = "deleted"

	BizSinkEndpointUsageTypeChangefeed        BizSinkEndpointUsageType = "changefeed"
	BizSinkEndpointUsageTypePremiumDMTask     BizSinkEndpointUsageType = "premium_dm_task"
	BizSinkEndpointUsageTypePremiumDMPrecheck BizSinkEndpointUsageType = "premium_dm_precheck"
)

type BizSinkEndpointDNSRecord struct {
	AvailabilityZone string `json:"availability_zone"`
	RecordName       string `json:"record_name"`
}

type ChangefeedTrafficExtension map[string]string

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

func (labels *ChangefeedTrafficExtension) Scan(input interface{}) error {
	return json.Unmarshal(input.([]byte), labels)
}

type TiCDCClusterSettingAction struct {
	CurrentActions []string `json:"current_actions"`
}
