package clusterevent

import (
	"encoding/json"
	"time"

	displayevent "github.com/tidbcloud/eventbus-datastruct/event"
	"gorm.io/gorm"
)

type Topic string

// TopicClusterEvent is the topic for cluster event, TopicMaintenanceWindowEvent is the topic for maintenance window event
// both internal pub sub and event bus, cluster event will be published to the topic named TopicClusterEvent
// both internal pub sub and event bus, maintenance window event will be published to the topic named TopicMaintenanceWindowEvent
const (
	TopicClusterEvent           Topic = "ClusterEvent"
	TopicDisplayEvent                 = Topic(displayevent.DisplayEventTopic)
	TopicMaintenanceWindowEvent Topic = "MaintenanceWindowEvent"
)

func (t Topic) String() string {
	return string(t)
}

type Meta struct {
	Topic       string
	UUID        string
	TimestampMs int64
}

type (
	EventType                  string
	MaintenanceWindowEventType string
)

// Note 1. notify clinic service team if you add or remove some events
// for more detail: refer to: https://pingcap.feishu.cn/wiki/wikcnJAZ642PH32aQzL8PKjtmge
const (
	EventTypeCreating     EventType = "ClusterCreating" // cluster is creating and created into metadb
	EventTypeCreated      EventType = "ClusterCreated"  // cluster is created and began to serve
	EventTypeInitializing EventType = "ClusterInitializing"
	EventTypeInitialized  EventType = "ClusterInitialized"
	EventTypePreparing    EventType = "ClusterPreparing"
	EventTypePrepared     EventType = "ClusterPrepared"
	EventTypeRestoring    EventType = "ClusterRestoring"
	EventTypeRestored     EventType = "ClusterRestored"
	EventTypeLaunching    EventType = "ClusterLaunching"
	EventTypeLaunched     EventType = "ClusterLaunched"
	EventTypeNormal       EventType = "ClusterNormal"

	// EventTypeApplying and EventTypeApplied are used for the operation of applying the cluster configuration or version.
	EventTypeApplying         EventType = "ClusterApplying"
	EventTypeApplied          EventType = "ClusterApplied"
	EventTypeShutdownUpgrade  EventType = "ClusterShutdownUpgrade"
	EventTypeShutdownUpgraded EventType = "ClusterShutdownUpgraded"

	// EventTypeScheduledResume is used for the operation of scheduled resume.
	EventTypeScheduledResume EventType = "ClusterScheduledResume"

	EventTypePausing                 EventType = "ClusterPausing"
	EventTypePaused                  EventType = "ClusterPaused"
	EventTypeResuming                EventType = "ClusterResuming"
	EventTypeResumed                 EventType = "ClusterResumed"
	EventTypeScaling                 EventType = "ClusterScaling"
	EventTypeScaled                  EventType = "ClusterScaled"
	EventTypeDeleting                EventType = "ClusterDeleting"
	EventTypeDeleted                 EventType = "ClusterDeleted"
	EventTypeCleared                 EventType = "ClusterCleared"
	EventTypeVolumeSnapshotRestoring EventType = "ClusterVolumeSnapshotRestoring"
	EventTypeVolumeSnapshotRestored  EventType = "ClusterVolumeSnapshotRestored"

	EventTypeInsufficientResource EventType = "ClusterInsufficientResource"

	// Serverless events
	EventTypeUpdatingPublicEndpoint EventType = "ClusterPublicEndpointUpdating"
	EventTypeUpdatedPublicEndpoint  EventType = "ClusterPublicEndpointUpdated"

	EventTypeUpdatingCapacity EventType = "ClusterCapacityUpdating"
	EventTypeUpdatedCapacity  EventType = "ClusterCapacityUpdated"

	EventTypeUpdatingAuditLogConfig EventType = "ClusterAuditLogConfigUpdating"
	EventTypeUpdatedAuditLogConfig  EventType = "ClusterAuditLogConfigUpdated"

	// NextGen private link events
	EventTypeCreatingPrivateLink EventType = "ClusterPrivateLinkCreating"
	EventTypeCreatedPrivateLink  EventType = "ClusterPrivateLinkCreated"

	// EventTypeProjectChanged is emitted when a cluster is moved to a different project.
	// The ProjectID field in ClusterEvent reflects the new project.
	// The old project ID is stored in ExtraInfo.OldProjectID.
	EventTypeProjectChanged      EventType = "ClusterProjectChanged"
	EventTypeRootPasswordRotated EventType = "ClusterRootPasswordRotated"

	// EventTypeStarterInventoryThresholdReached is emitted when Starter inventory
	// usage reaches a configured threshold.
	EventTypeStarterInventoryThresholdReached EventType = "StarterInventoryThresholdReached"

	// Resource pool events
	EventTypeResourcePoolDefaultSet           EventType = "ResourcePoolDefaultSet"
	EventTypeResourcePoolInstanceLimitUpdated EventType = "ResourcePoolInstanceLimitUpdated"

	// Resource pool instance events — fired when an instance is created/deleted on a resource pool.
	// These events have ClusterType=tidbx_resource_pool and ClusterID=resource pool ID.
	EventTypeInstanceCreatingOnResourcePool EventType = "InstanceCreatingOnResourcePool"
	EventTypeInstanceCreatedOnResourcePool  EventType = "InstanceCreatedOnResourcePool"
	EventTypeInstanceDeletingOnResourcePool EventType = "InstanceDeletingOnResourcePool"
	EventTypeInstanceDeletedOnResourcePool  EventType = "InstanceDeletedOnResourcePool"
)

type ClusterComponent struct {
	ComponentType     string `json:"component_type"`
	SpuName           string `json:"tier_name"` // the name of tier used by this component, for example: "8C16G"
	SpuDisplayName    string `json:"tier_display_name"`
	SpuKey            string `json:"tier_key"`      // the key of tier, for example: '2C4G:DEPLOY_TYPE_DEDICATED:COMPONENT_TYPE_TIKV'
	SkuKey            string `json:"tier_unit_key"` // the key of tier unit, for example: 'aws:tikv:t4g.medium:gp3:4000:400'
	Quantity          uint32 `json:"quantity"`      // the node quantity of this component
	StorageSizeGi     uint32 `json:"storage_size_gi"`
	StorageIops       uint32 `json:"storage_iops"`
	StorageThroughput uint32 `json:"storage_throughput_mibps"`
	AdditionalIops    uint32 `json:"additional_iops"` // data disk additional iops, only for iops mapping dynamic storage
	VolumeType        string `json:"volume_type"`
	RaftLogVolumeType string `json:"raft_log_volume_type"`
	RaftLogIops       uint32 `json:"raft_log_iops"` // raft log disk iops
	RaftLogThroughput uint32 `json:"raft_log_throughput_mibps"`
}

type OperationSource string

const (
	OperationSourceModifyClusterVolume       OperationSource = "ModifyClusterVolume"
	OperationSourceScaleClusterComponents    OperationSource = "ScaleClusterComponents"
	OperationSourceScaleClusterPdComponent   OperationSource = "ScaleClusterPdComponent"
	OperationSourceScaleClusterTiProxy       OperationSource = "ScaleClusterTiProxyComponent"
	OperationSourceApplyCluster              OperationSource = "ApplyCluster"
	OperationSourceScaleClusterWithComponent OperationSource = "ScaleClusterWithMultiComponent"
)

type OperationContext struct {
	SourceOperation     string                       `json:"source_operation,omitempty"`
	RequestedFields     []string                     `json:"requested_fields,omitempty"`
	RequestedComponents []*RequestedClusterComponent `json:"requested_components,omitempty"`
	ComponentOperations map[string][]string          `json:"component_operations,omitempty"`
	ApplyMask           *ClusterApplyMask            `json:"apply_mask,omitempty"`
}

type RequestedClusterComponent struct {
	ComponentType        string                       `json:"component_type,omitempty"`
	TidbGroupID          uint64                       `json:"tidb_group_id,string,omitempty"`
	TidbGroupDisplayName string                       `json:"tidb_group_display_name,omitempty"`
	SpuName              string                       `json:"tier_name,omitempty"`
	SpuDisplayName       string                       `json:"tier_display_name,omitempty"`
	SpuKey               string                       `json:"tier_key,omitempty"`
	SkuKey               string                       `json:"tier_unit_key,omitempty"`
	Quantity             *uint32                      `json:"quantity,omitempty"`
	Storages             map[string]*RequestedStorage `json:"storages,omitempty"`
}

type RequestedStorage struct {
	DiskType        string `json:"disk_type,omitempty"`
	SizeGi          uint32 `json:"size_gi,omitempty"`
	Iops            uint32 `json:"iops,omitempty"`
	ThroughputMibps uint32 `json:"throughput_mibps,omitempty"`
	VolumeType      string `json:"volume_type,omitempty"`
	AdditionalIops  uint32 `json:"additional_iops,omitempty"`
}

type ClusterApplyMask struct {
	Version             bool                  `json:"version,omitempty"`
	Strategy            string                `json:"strategy,omitempty"`
	HotReload           bool                  `json:"hot_reload,omitempty"`
	ComponentApplyMasks []*ComponentApplyMask `json:"component_apply_masks,omitempty"`
}

type ComponentApplyMask struct {
	ComponentType string `json:"component_type,omitempty"`
	Configuration bool   `json:"configuration,omitempty"`
	Image         bool   `json:"image,omitempty"`
}

type ClusterTopology struct {
	Components       map[string]*ClusterComponent `json:"components"`
	ExTidbNodeGroups []*TidbNodeGroup             `json:"ex_tidb_node_groups,omitempty"` // without default tidb node group
}

type TidbNodeGroup struct {
	ID                    uint64 `json:"id"`
	DisplayName           string `json:"display_name"`
	TidbNodeQuantity      uint32 `json:"tidb_node_quantity"`
	TiProxyQuantity       uint32 `json:"tiproxy_node_quantity"`
	TiProxySpuName        string `json:"tiproxy_tier_name"`
	TiProxySpuDisplayName string `json:"tiproxy_tier_display_name"`
}

type EventExtraInfo struct {
	DurationSeconds *int64 `json:"duration_seconds,string,omitempty"`
	OldMaxRcu       *int64 `json:"old_max_rcu,string,omitempty"`
	NewMaxRcu       *int64 `json:"new_max_rcu,string,omitempty"`

	// These fields below are only available for ClusterScaling event now.
	TopologyBefore *ClusterTopology `json:"topology_before,omitempty"`
	TopologyAfter  *ClusterTopology `json:"topology_after,omitempty"`

	// OperationContext carries upstream request facts for display and audit consumers.
	OperationContext *OperationContext `json:"operation_context,omitempty"`

	// These fields below are only available for Upgrade event now.
	OldVersion *string `json:"old_version,omitempty"`
	NewVersion *string `json:"new_version,omitempty"`

	// These fields below are only available for Resume event now.
	PausePlanType string `json:"pause_plan_type,omitempty"`

	// These fields below are only available for ClusterAuditLogConfigUpdating and ClusterAuditLogConfigUpdated event now.
	AuditLogConfig string `json:"audit_log_config,omitempty"`

	// These fields below are only available for ClusterProjectChanged event now.
	OldProjectID uint64 `json:"old_project_id,string,omitempty"`

	// These fields below are only available for resource pool created event.
	ResourcePoolTopology string `json:"resource_pool_topology,omitempty"`

	// These fields below are only available for resource pool instance limit updated event.
	InstanceLimitBefore *uint64 `json:"instance_limit_before,string,omitempty"`
	InstanceLimitAfter  *uint64 `json:"instance_limit_after,string,omitempty"`

	// These fields below are only available for resource pool set default event.
	Cloud  string `json:"cloud,omitempty"`
	Region string `json:"region,omitempty"`

	// These fields below are available for plan replayer updates.
	PlanReplayerEnabled    *bool  `json:"plan_replayer_enabled,omitempty"`
	PlanReplayerExpireTime string `json:"plan_replayer_expire_time,omitempty"`

	// These fields below are only available for resource pool instance events
	// (InstanceCreatingOnResourcePool/InstanceCreatedOnResourcePool/InstanceDeletingOnResourcePool/InstanceDeletedOnResourcePool).
	InstanceID *uint64 `json:"instance_id,string,omitempty"`

	// These fields below are available for starter inventory threshold events.
	InventoryLimit            *int64 `json:"inventory_limit,string,omitempty"`
	InventoryCurrentCount     *int64 `json:"inventory_current_count,string,omitempty"`
	InventoryThresholdPercent *int64 `json:"inventory_threshold_percent,string,omitempty"`
}

type Event interface {
	MarshalJsonPayload() (string, error)
	SetEventCreatedAtMs(t int64)
	GetEventCreatedAtMs() int64
	GetTargetTopic() Topic
	GetEventType() string
}

type DisplayEvent displayevent.DisplayEvent

func (ev *DisplayEvent) MarshalJsonPayload() (string, error) {
	bs, e := json.Marshal(ev)
	return string(bs), e
}

func (ev *DisplayEvent) SetEventCreatedAtMs(ms int64) {
	ev.HappenedAtMs = uint64(ms)
}

func (ev *DisplayEvent) GetEventCreatedAtMs() int64 {
	return int64(ev.HappenedAtMs)
}

func (ev *DisplayEvent) GetTargetTopic() Topic {
	return TopicDisplayEvent
}

func (ev *DisplayEvent) GetEventType() string {
	return ev.DisplayEventType
}

type ClusterEvent struct {
	TenantID  uint64 `json:"tenant_id,string"`
	ProjectID uint64 `json:"project_id,string"`
	ClusterID uint64 `json:"cluster_id,string"`
	TraceID   string `json:"trace_id,omitempty"`

	// The operator's userId who did an operation which result in this event.
	// It may be zero that presents the operator is unknown or this event without operator.
	OperatorID       uint64 `json:"operator_id,string"`
	UserEmail        string `json:"user_email"`
	OpenAPIPublicKey string `json:"openapi_public_key"`

	// id of operation, it is used to associate with cluster_operation record.
	// It may be zero that presents this event without any cluster operation.
	// NOTE 1: Due to some technological problem, this field always is 0 in xxx-ing event.
	OperationID uint64 `json:"operation_id,string"`

	IsDevTier        bool            `json:"is_dev_tier"`
	Status           string          `json:"status"` // refer to idl/core/core/core.proto:ClusterStatus
	EventType        EventType       `json:"event_type"`
	EventCreatedAtMs int64           `json:"event_created_at_ms,string"` // unix timestamp in milliseconds t.UnixMilli()
	ExtraInfo        *EventExtraInfo `json:"extra_info,omitempty"`
	ServicePlan      string          `json:"service_plan,omitempty"`
	ClusterType      string          `json:"cluster_type,omitempty"`
}

type ClusterType string

const ClusterTypeTidbxInstance = "tidbx_instance"
const ClusterTypeDedicatedCluster = "dedicated_cluster"
const ClusterTypeTidbxResourcePool = "tidbx_resource_pool"

func (ev *ClusterEvent) MarshalJsonPayload() (string, error) {
	bs, e := json.Marshal(ev)
	return string(bs), e
}

func (ev *ClusterEvent) SetEventCreatedAtMs(ms int64) {
	ev.EventCreatedAtMs = ms
}

func (ev *ClusterEvent) GetEventCreatedAtMs() int64 {
	return ev.EventCreatedAtMs
}

func (ev *ClusterEvent) GetTargetTopic() Topic {
	return TopicClusterEvent
}

func (ev *ClusterEvent) GetEventType() string {
	return string(ev.EventType)
}

type ClusterThresholdStateChanged struct {
	TenantID    uint64
	ProjectID   uint64
	ClusterID   uint64
	TriggerTime time.Time

	IsDevTier bool

	IngressThrottled bool
	EgressThrottled  bool
}

func (ev *ClusterThresholdStateChanged) AnyThrottled() bool {
	return ev.IngressThrottled || ev.EgressThrottled
}

type MaintenanceTask struct {
	ID                 uint64 `json:"id,string"`
	Status             string `json:"status"`                      // Refer to idl/core/core/core.proto:MaintenanceTask.Status
	ScheduledApplyTime int64  `json:"scheduled_apply_time,string"` // time.Unix()
	ClusterIDs         []uint64
}

type MaintenanceTaskExecution struct {
	ID                uint64 `json:"id,string"`
	MaintenanceTaskID uint64 `json:"maintenance_task_id,string"`
	ClusterID         uint64 `json:"cluster_id,string"`
	Status            string `json:"status"` // Refer to idl/core/core/core.proto:MaintenanceTaskExecution.Status
}

type MaintenanceWindowEvent struct {
	ProjectID                uint64                     `json:"project_id,string"`
	TraceID                  string                     `json:"trace_id,omitempty"`
	EventType                MaintenanceWindowEventType `json:"event_type"`
	EventCreatedAtMs         int64                      `json:"event_created_at_ms,string"` // unix timestamp in milliseconds t.UnixMilli()
	MaintenanceTask          *MaintenanceTask           `json:"maintenance_task,omitempty"`
	MaintenanceTaskExecution *MaintenanceTaskExecution  `json:"maintenance_task_execution,omitempty"`
}

func (ev *MaintenanceWindowEvent) MarshalJsonPayload() (string, error) {
	bs, e := json.Marshal(ev)
	return string(bs), e
}

func (ev *MaintenanceWindowEvent) SetEventCreatedAtMs(ms int64) {
	ev.EventCreatedAtMs = ms
}

func (ev *MaintenanceWindowEvent) GetEventCreatedAtMs() int64 {
	return ev.EventCreatedAtMs
}

func (ev *MaintenanceWindowEvent) GetTargetTopic() Topic {
	return TopicMaintenanceWindowEvent
}

func (ev *MaintenanceWindowEvent) GetEventType() string {
	return string(ev.EventType)
}

const (
	MaintenanceWindowEventTypeTaskCreated       MaintenanceWindowEventType = "TaskCreated"
	MaintenanceWindowEventTypeTaskUpdated       MaintenanceWindowEventType = "TaskUpdated"
	MaintenanceWindowEventTypeTaskDone          MaintenanceWindowEventType = "TaskDone"
	MaintenanceWindowEventTypeTaskRunning       MaintenanceWindowEventType = "TaskRunning"
	MaintenanceWindowEventTypeExecutionRunning  MaintenanceWindowEventType = "ExecutionRunning"
	MaintenanceWindowEventTypeExecutionDone     MaintenanceWindowEventType = "ExecutionDone"
	MaintenanceWindowEventTypeExecutionCanceled MaintenanceWindowEventType = "ExecutionCanceled"
)

type Status string

const (
	StatusCreated Status = "created"
	StatusSent    Status = "sent"
)

type EventModel struct {
	gorm.Model

	// Indexes managed by migration 2026082401 (do not rely on AutoMigrate tags):
	// - idx_events_topic_status_deleted_ts: (topic(191), status, deleted_at, timestamp, id)
	// - idx_events_status_created: (status, created_at)
	Topic   string `gorm:"index"`
	UUID    string `gorm:"index:,unique"`
	Payload string
	// Millisecond precision."9999-12-31 23:59:59.499" is the max value of datetime(3) in mysql.
	Timestamp time.Time `gorm:"precision:3"`
	Status    Status    `gorm:"index;type:ENUM('created','sent');default:'created'"`
}

func (EventModel) TableName() string {
	return "events"
}
