package entity

import "strings"

import "time"

type (
	DataPipelineStatus            string
	DataPipelineSyncMode          string
	DataPipelineDestinationType   string
	DataPipelineStagingType       string
	DataPipelineSnapshotEngine    string
	DataPipelineSnapshotFormat    string
	DataPipelineIncrementalEngine string
	DataPipelineIncrementalFormat string
)

const (
	DataPipelineStatusCreating  DataPipelineStatus = "creating"
	DataPipelineStatusRunning   DataPipelineStatus = "running"
	DataPipelineStatusPausing   DataPipelineStatus = "pausing"
	DataPipelineStatusPaused    DataPipelineStatus = "paused"
	DataPipelineStatusEditing   DataPipelineStatus = "editing"
	DataPipelineStatusResuming  DataPipelineStatus = "resuming"
	DataPipelineStatusDeleting  DataPipelineStatus = "deleting"
	DataPipelineStatusFailed    DataPipelineStatus = "failed"
	DataPipelineStatusCompleted DataPipelineStatus = "completed"
)

const (
	DataPipelineSyncModeFull               DataPipelineSyncMode = "full"
	DataPipelineSyncModeFullAndIncremental DataPipelineSyncMode = "full_and_incremental"
)

const (
	DataPipelineDestinationTypeTiDBLake  DataPipelineDestinationType = "tidb_lake"
	DataPipelineDestinationTypeSnowflake DataPipelineDestinationType = "snowflake"
)

const (
	DataPipelineStagingTypeCustomerManagedS3 DataPipelineStagingType = "customer_managed_s3"
	DataPipelineStagingTypeTiDBManaged       DataPipelineStagingType = "tidb_managed"
)

const (
	DataPipelineSnapshotEngineDumpling DataPipelineSnapshotEngine = "dumpling"
	DataPipelineSnapshotEngineBR       DataPipelineSnapshotEngine = "br"

	DataPipelineSnapshotFormatCSV DataPipelineSnapshotFormat = "csv"
)

const (
	DataPipelineIncrementalEngineTiCDC DataPipelineIncrementalEngine = "ticdc"

	DataPipelineIncrementalFormatCanalJSON DataPipelineIncrementalFormat = "canal_json"
)

// DataPipelineParams is the user-visible desired configuration. Credential
// values in this persisted type are ciphertext produced by the model layer;
// callers must never place plaintext credentials here.
type DataPipelineParams struct {
	Version     uint16                        `json:"version"`
	SyncMode    DataPipelineSyncMode          `json:"sync_mode"`
	Source      DataPipelineSourceConfig      `json:"source"`
	Staging     DataPipelineStagingConfig     `json:"staging"`
	Destination DataPipelineDestinationConfig `json:"destination"`
}

type DataPipelineSourceConfig struct {
	Username           string `json:"username"`
	PasswordCiphertext string `json:"password_ciphertext"`
	// TableFilter is passed through to Export and the Changefeed exactly as the
	// user wrote it; this service does not expand or normalize the rules.
	TableFilter   []string `json:"table_filter"`
	CaseSensitive bool     `json:"case_sensitive"`
	// MatchedTableCount is how many tables the filter selected when it was last
	// resolved, at create or edit. It is what tells a caller that a filter
	// matched nothing, which is allowed but is almost never what was meant, and
	// it is the only such signal a snapshot-only Pipeline has: the destination's
	// live table counters come from the CDC run, which it does not have.
	//
	// A pointer because zero is a real answer. Rows written before this existed
	// carry nil, which reads as "not recorded" rather than "matched nothing".
	MatchedTableCount *int32 `json:"matched_table_count,omitempty"`
}

type DataPipelineStagingConfig struct {
	Type DataPipelineStagingType `json:"type"`
	S3   DataPipelineS3Config    `json:"s3"`
}

// DataPipelineS3AuthType selects how the staging bucket is reached. All three
// children support both modes, so the choice is made once on the Pipeline and
// forwarded to each of them in whatever shape that child's API takes.
type DataPipelineS3AuthType string

const (
	DataPipelineS3AuthTypeAccessKey DataPipelineS3AuthType = "access_key"
	DataPipelineS3AuthTypeRoleARN   DataPipelineS3AuthType = "role_arn"
)

// DataPipelineS3Provider is the object-storage provider behind the staging
// area. The two are addressed and reached differently - Alibaba OSS needs a
// custom endpoint, an oss:// URI, and tells TiCDC to skip the AWS-only bucket
// region probe - so the provider has to be recorded rather than inferred.
type DataPipelineS3Provider string

const (
	// DataPipelineS3ProviderAWS is the zero value on purpose: every row written
	// before OSS support is AWS, and must keep reading as AWS.
	DataPipelineS3ProviderAWS        DataPipelineS3Provider = ""
	DataPipelineS3ProviderAlibabaOSS DataPipelineS3Provider = "alibaba_oss"
)

type DataPipelineS3Config struct {
	Bucket     string `json:"bucket"`
	BasePrefix string `json:"base_prefix,omitempty"`
	Region     string `json:"region"`
	Endpoint   string `json:"endpoint,omitempty"`
	// Provider is empty for AWS, which keeps pre-OSS rows reading correctly.
	Provider DataPipelineS3Provider `json:"provider,omitempty"`
	// AuthType decides which of the two credential shapes below is populated.
	// A row written before role ARN support has neither an auth type nor a role
	// ARN, so an empty value reads as access key.
	AuthType                  DataPipelineS3AuthType `json:"auth_type,omitempty"`
	AccessKeyID               string                 `json:"access_key_id,omitempty"`
	SecretAccessKeyCiphertext string                 `json:"secret_access_key_ciphertext,omitempty"`
	RoleARN                   string                 `json:"role_arn,omitempty"`
	// The customer's SQS queue, if the destination is to be notified of new
	// objects rather than poll for them. Optional, and editable: unlike every
	// other field here it does not decide where data lands, only how quickly
	// its arrival is noticed. Empty means polling.
	SQSQueueURL string `json:"sqs_queue_url,omitempty"`
	SQSRegion   string `json:"sqs_region,omitempty"`
}

// UsesSQS reports whether the destination is notified rather than polling.
//
// Trimmed, so that "is a queue configured?" has one answer everywhere. The
// validator has always asked it that way, and a value that differs between the
// two would plan a notified Pipeline out of a queue the validator considered
// absent.
func (c DataPipelineS3Config) UsesSQS() bool {
	return strings.TrimSpace(c.SQSQueueURL) != ""
}

// UsesOSS reports whether the staging area is Alibaba OSS rather than AWS S3.
func (c DataPipelineS3Config) UsesOSS() bool {
	return c.Provider == DataPipelineS3ProviderAlibabaOSS
}

// UsesRoleARN reports whether this staging configuration assumes a role rather
// than carrying a static key pair.
func (c DataPipelineS3Config) UsesRoleARN() bool {
	return c.AuthType == DataPipelineS3AuthTypeRoleARN
}

type DataPipelineDestinationConfig struct {
	Type DataPipelineDestinationType `json:"type"`
	// The Lake the warehouse belongs to. Fixed at creation: moving a Pipeline
	// between Lakes would leave its datasource, its task and everything already
	// loaded behind.
	LakeOrgID    string                   `json:"lake_org_id,omitempty"`
	Warehouse    string                   `json:"warehouse"`
	TargetNaming DataPipelineTargetNaming `json:"target_naming"`
}

// DataPipelineTargetNaming is passed straight to the Lake task, which owns the
// naming semantics.
type DataPipelineTargetNaming struct {
	DatabasePrefix string `json:"database_prefix,omitempty"`
	DatabaseSuffix string `json:"database_suffix,omitempty"`
	TablePrefix    string `json:"table_prefix,omitempty"`
	TableSuffix    string `json:"table_suffix,omitempty"`
}

// DataPipelineExecutionPlan is selected by the service at Create time and is
// immutable for the lifetime of the Pipeline. It deliberately records engine
// and encoding choices that users cannot select.
type DataPipelineExecutionPlan struct {
	Version         uint16                       `json:"version"`
	DestinationType DataPipelineDestinationType  `json:"destination_type"`
	Snapshot        DataPipelineSnapshotPlan     `json:"snapshot"`
	Incremental     *DataPipelineIncrementalPlan `json:"incremental,omitempty"`
}

type DataPipelineSnapshotPlan struct {
	Engine DataPipelineSnapshotEngine `json:"engine"`
	Format DataPipelineSnapshotFormat `json:"format"`
}

type DataPipelineIncrementalPlan struct {
	Engine DataPipelineIncrementalEngine `json:"engine"`
	Format DataPipelineIncrementalFormat `json:"format"`
}

type DeliveryIntervalPlan struct {
	Version                      uint16 `json:"version"`
	EndToEndSeconds              int32  `json:"end_to_end_seconds"`
	ProducerFlushIntervalSeconds int32  `json:"producer_flush_interval_seconds"`
	ConsumerPollIntervalSeconds  int32  `json:"consumer_poll_interval_seconds"`
}

// DataPipelineChildResources is decided when the Pipeline row is inserted, not
// discovered afterwards. Every name is derived from the Pipeline ID and the
// Changefeed ID is allocated up front, so "did my create already happen?" is
// answered by a lookup on a key we chose, with no state to reconcile.
//
// Lake has no separate identifier at all: the name is the resource, so nothing
// about the destination needs recording. Export still reports an ID of its own,
// which is kept for reads until its API accepts a caller-supplied one.
type DataPipelineChildResources struct {
	ExportName         string `json:"export_name,omitempty"`
	ExportID           string `json:"export_id,omitempty"`
	ChangefeedName     string `json:"changefeed_name,omitempty"`
	ChangefeedID       uint64 `json:"changefeed_id,omitempty"`
	LakeDataSourceName string `json:"lake_data_source_name,omitempty"`
	LakeDataSourceID   string `json:"lake_data_source_id,omitempty"`
	LakeTaskName       string `json:"lake_task_name,omitempty"`
}

type DataPipelineError struct {
	Code       string     `json:"code"`
	Message    string     `json:"message"`
	Retryable  bool       `json:"retryable"`
	OccurredAt *time.Time `json:"occurred_at,omitempty"`
}

// DataPipelineObservation contains the last good downstream positions. On a
// polling failure workers update LastAttemptAt and PollError but preserve the
// checkpoint/resolved values and LastSucceededAt.
type DataPipelineObservation struct {
	LastAttemptAt           *time.Time `json:"last_attempt_at,omitempty"`
	LastSucceededAt         *time.Time `json:"last_succeeded_at,omitempty"`
	ExportStatus            string     `json:"export_status,omitempty"`
	ExportErrorMessage      string     `json:"export_error_message,omitempty"`
	ChangefeedStatus        string     `json:"changefeed_status,omitempty"`
	ChangefeedErrorMessage  string     `json:"changefeed_error_message,omitempty"`
	LakeDataSourceStatus    string     `json:"lake_data_source_status,omitempty"`
	LakeIntegrationStatus   string     `json:"lake_integration_status,omitempty"`
	LakeErrorMessage        string     `json:"lake_error_message,omitempty"`
	ChangefeedCheckpointTSO uint64     `json:"changefeed_checkpoint_tso,omitempty"`
	ChangefeedCheckpointAt  *time.Time `json:"changefeed_checkpoint_at,omitempty"`
	LakeResolvedTSO         uint64     `json:"lake_resolved_tso,omitempty"`
	// LakeFullSyncCompleted drives both the full-only Completed transition and
	// the derived INITIAL_LOADING/STREAMING phase, so it must be durable.
	LakeFullSyncCompleted bool               `json:"lake_full_sync_completed,omitempty"`
	LakeResolvedAt        *time.Time         `json:"lake_resolved_at,omitempty"`
	LatencySeconds        int64              `json:"latency_seconds,omitempty"`
	Stale                 bool               `json:"stale,omitempty"`
	PollError             *DataPipelineError `json:"poll_error,omitempty"`

	// The counters the destination reports for its latest incremental run.
	// They describe that one run and not the Pipeline's lifetime, so they can
	// drop when a new run starts and must not be read as a running total.
	//
	// Only the CDC run reports them, so a snapshot-only Pipeline leaves every
	// one at zero for its whole life. They also count what a run has handled
	// rather than what it covers: a healthy Pipeline whose tables have had no
	// writes reports zero for all of them, TablesActive included. None of them
	// therefore says whether anything is selected; MatchedTableCount does.
	RowsSynced      uint64 `json:"rows_synced,omitempty"`
	InsertCount     uint64 `json:"insert_count,omitempty"`
	UpdateCount     uint64 `json:"update_count,omitempty"`
	DeleteCount     uint64 `json:"delete_count,omitempty"`
	TablesActive    int32  `json:"tables_active,omitempty"`
	TablesError     int32  `json:"tables_error,omitempty"`
	DatabasesActive int32  `json:"databases_active,omitempty"`
}
