package entity

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

//go:generate ../../../tools/bin/goqueryset -in import_into.go
type ImportIntoStatus string

const (
	ImportIntoStatusUnknown   ImportIntoStatus = "unknown"
	ImportIntoStatusPreparing ImportIntoStatus = "preparing"
	ImportIntoStatusImporting ImportIntoStatus = "importing"
	ImportIntoStatusCompleted ImportIntoStatus = "completed"
	ImportIntoStatusFailed    ImportIntoStatus = "failed"
	ImportIntoStatusCanceling ImportIntoStatus = "canceling"
	ImportIntoStatusCanceled  ImportIntoStatus = "canceled"
	ImportIntoStatusScalingUp ImportIntoStatus = "scaling"
)

type ArchType string

const (
	CloudNativeArch ArchType = "cloudnative"
	ThreeLayerArch  ArchType = "3layer"
)

const (
	ServerTierNextGeneration = "next_generation"
)

type ImportTableResult string

const (
	ImportTableResultSuccess ImportTableResult = "success"
	ImportTableResultWarning ImportTableResult = "warning"
	ImportTableResultError   ImportTableResult = "error"
)

const (
	ImportIntoSchemaOnlySuccessMessage = "Schema created successfully. No data files found."

	ImportIntoJobStatusPending  = "pending"
	ImportIntoJobStatusRunning  = "running"
	ImportIntoJobStatusCanceled = "canceled"
	ImportIntoJobStatusFailed   = "failed"
	ImportIntoJobStatusFinished = "finished"
)

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

		Name       string `gorm:"type:VARCHAR(128)"`
		ClusterID  ID     `gorm:"index;not null" valid:"required"`
		ProjectID  ID
		TenantID   ID               `gorm:"not null"`
		Status     ImportIntoStatus `gorm:"type:ENUM('unknown','preparing', 'importing','completed', 'failed', 'canceling', 'canceled', 'scaling');not null;default:'preparing';index;index:idx_import_into_tasks_custom_status_reconcile,priority:2"` // enum
		Message    string           `gorm:"type:LONGTEXT"`                                                                                                                                                                                             // failed message
		StartedAt  *time.Time       `gorm:"type:TIMESTAMP"`
		FinishedAt *time.Time       `gorm:"type:TIMESTAMP;index"`
		CreatorID  uint64
		Creator    string `gorm:"type:VARCHAR(255)"`

		TargetUsername string `gorm:"type:VARCHAR(255)"`
		TargetPassword string `gorm:"type:VARCHAR(1024)"`

		CustomCSVFormat string `gorm:"type:LONGTEXT"`
		Options         string `gorm:"type:LONGTEXT"`

		// Source data info
		TotalSize  uint64
		TotalFiles uint32
		SourceURI  string `gorm:"type:LONGTEXT"`

		ImportResults ImportIntoSubTasks `gorm:"type:JSON"`

		ServerTier string   `gorm:"type:VARCHAR(64)"`
		ArchType   ArchType `gorm:"type:ENUM('cloudnative','3layer');not null;default:'cloudnative'"`

		GroupKey string `gorm:"type:VARCHAR(191)"`

		// CustomStatusRegistered remains true until the Import Into pause mutex is
		// successfully removed from tidb-management-service.
		CustomStatusRegistered bool       `gorm:"not null;default:false;index:idx_import_into_tasks_custom_status_reconcile,priority:1"`
		CustomStatusRetryAt    *time.Time `gorm:"type:TIMESTAMP;index:idx_import_into_tasks_custom_status_reconcile,priority:3"`
	}

	ImportIntoPrecheckTask struct {
		Updatable

		ClusterID  ID                           `gorm:"index;not null"`
		SourceType string                       `gorm:"type:VARCHAR(32);not null"`
		TableMetas ImportIntoPrecheckTableMetas `gorm:"type:JSON"`
	}
)

type (
	ImportIntoSubTask struct {
		DataBase                  string            `json:"data_base"`
		TableName                 string            `json:"table_name"`
		Result                    ImportTableResult `json:"result"`
		Message                   string            `json:"message"`
		ImportStartedAt           *time.Time        `json:"import_started_at"`
		ImportFinishedAt          *time.Time        `json:"import_finished_at"`
		PostImportStartedAt       *time.Time        `json:"post_import_started_at"`
		PostImportFinishedAt      *time.Time        `json:"post_import_finished_at"`
		ImportIntoPhase           string            `json:"import_into_phase"`
		ImportIntoJobID           uint64            `json:"import_into_job_id"`
		ImportIntoJobStatus       string            `json:"import_into_job_status"`
		ImportIntoFileType        string            `json:"import_into_file_type"`
		ImportIntoFileCompression string            `json:"import_into_file_compression"`
		ImportIntoSchemaFile      string            `json:"import_into_schema_file"`
		ImportIntoFilesWildcard   string            `json:"import_into_files_wildcard"`
		ImportIntoTotalSize       uint64            `json:"import_into_total_size"`
		ImportedRows              int64             `json:"imported_rows"`
		// Deprecated, no need this field
		ExecutedNodes int    `json:"executed_nodes"`
		CurrentStep   string `json:"currentStep"`
		// The total size of the current step to be processed, in a human-readable format.
		CurrentStepTotalSize string `json:"current_step_total_size"`
		// The current step processing progress, as a percentage.
		CurrentStepProgressPercent int32 `json:"current_step_progress_percent"`
		// The ETA of the current step.
		CurrentStepETA string `json:"current_step_eta"`
		// The processed data size of the current step, in a human-readable format.
		CurrentStepProcessedDataSize string `json:"current_step_processed_data_size"`
		// The custom columns of the table to be imported. An empty list means matching the columns of the input file(s).
		Columns          []string `json:"columns"`
		CurrentStepSpeed string   `json:"current_step_speed"`
	}

	ImportIntoSubTasks struct {
		// ControlPlaneFailed indicates the task failed in control-plane/workflow
		// handling rather than from a terminal IMPORT INTO SQL execution result.
		ControlPlaneFailed bool                `json:"control_plane_failed,omitempty"`
		SubTasks           []ImportIntoSubTask `json:"sub_tasks"`
	}

	ImportIntoPrecheckTableMeta struct {
		Database      string `json:"database"`
		Table         string `json:"table"`
		FilesWildcard string `json:"files_wildcard"`
	}
)

func (r ImportIntoSubTasks) Value() (driver.Value, error) {
	return marshalJSONValue(r)
}

func (r *ImportIntoSubTasks) Scan(input any) error {
	return unmarshalJSONValue(input, r)
}

type ImportIntoPrecheckTableMetas []ImportIntoPrecheckTableMeta

func (r ImportIntoPrecheckTableMetas) Value() (driver.Value, error) {
	return marshalJSONValue(r)
}

func (r *ImportIntoPrecheckTableMetas) Scan(input any) error {
	return unmarshalJSONValue(input, r)
}

func marshalJSONValue(v any) (driver.Value, error) {
	b, err := json.Marshal(v)
	return string(b), err
}

func unmarshalJSONValue(input any, dest any) error {
	if input == nil {
		return nil
	}

	var raw []byte
	switch v := input.(type) {
	case []byte:
		raw = v
	case string:
		raw = []byte(v)
	default:
		return fmt.Errorf("unsupported json scan type %T", input)
	}
	if len(raw) == 0 {
		return nil
	}
	return json.Unmarshal(raw, dest)
}
