package migrate_record

import (
	"github.com/pingcap/dbaas/billing2/db/entity"
	"gorm.io/gorm"
)

var _ = AddMigrateRecord(
	"2026-08-07-pricing-events",
	func(db *gorm.DB) error {
		err := db.AutoMigrate(&entity.PricingEvent{})
		if err != nil {
			return err
		}
		err = db.AutoMigrate(&entity.TenantUnit{})
		if err != nil {
			return err
		}
		err = db.AutoMigrate(&entity.TenantUsage{})
		if err != nil {
			return err
		}
		return nil
	},
)

// 2026-08-17-metering-events-status-index-details-text
//
// Must be a NEW record ID, not an edit of 2026-08-07-pricing-events above:
// gormigrate skips any ID already present in billing2_migrations, and on
// warm DBs that ID is already recorded, so in-place edits would never run.
//
//   - idx_status on metering_events.status. The pricing worker claims events
//     with `WHERE status = 'to_price' ORDER BY id ASC FOR UPDATE SKIP LOCKED`
//     (pricing_worker.go claimMeteringEvent) and the retry worker with
//     `WHERE status = 'price_failed' AND updated_at < ?` — both currently
//     full-scan. InnoDB secondary indexes implicitly append the PK, so a
//     single-column (status) index serves both the equality lookup and the
//     id ordering.
//   - details column must be text. The entity tag says text, but warm DBs
//     may carry a varchar column from an earlier schema; long price-failure
//     detail strings would then fail on insert/update.
//   - pricing_comparison_logs for the pricing reconciliation worker: one row
//     per anomalous pricing event (one cost_id = one row), per-field diffs
//     as a JSON array. AutoMigrate is sufficient — the table is new.
//
// Idempotency: AutoMigrate covers fresh DBs via the entity tag; both DDL
// steps are guarded by information_schema lookups for warm/partial DBs.
var _ = AddMigrateRecord(
	"2026-08-17-metering-events-status-index-details-text",
	func(db *gorm.DB) error {
		var idxCount int
		if err := db.Raw(
			"SELECT COUNT(*) FROM information_schema.statistics "+
				"WHERE table_schema = DATABASE() AND table_name = ? AND index_name = ?",
			"metering_events", "idx_status",
		).Scan(&idxCount).Error; err != nil {
			return err
		}
		if idxCount == 0 {
			if err := db.Exec(
				"CREATE INDEX `idx_status` ON `metering_events` (`status`)",
			).Error; err != nil {
				return err
			}
		}

		var detailsType string
		if err := db.Raw(
			"SELECT data_type FROM information_schema.columns "+
				"WHERE table_schema = DATABASE() AND table_name = ? AND column_name = ?",
			"metering_events", "details",
		).Scan(&detailsType).Error; err != nil {
			return err
		}
		if detailsType != "" && detailsType != "text" {
			if err := db.Exec(
				"ALTER TABLE `metering_events` MODIFY COLUMN `details` text COMMENT 'Processing details'",
			).Error; err != nil {
				return err
			}
		}

		return db.AutoMigrate(&entity.PricingComparisonLog{})
	},
)

// 2026-08-20-pricing-comparison-logs-raw-pricing
//
// Shadow pricing events no longer persist to pricing_events at all:
// their complete output lives in pricing_comparison_logs (pricing =
// pipeline-side JSON, raw = old-path raw_user_unit_cost JSON), so
// pricing_events carries authoritative rows only and needs no shadow
// column. This record reconciles warm DBs (dev/staging) with the
// revised schema:
//
//   - DROP pricing_events.shadow if present (added on some warm DBs by
//     the since-deleted 2026-08-19 record; fresh DBs never had it).
//   - ADD pricing_comparison_logs.raw / pricing (text) — warm DBs that
//     ran 2026-08-17 carry the older shape without them.
//   - DROP pricing_comparison_logs.event_id if present (no longer
//     meaningful: shadow runs have no pricing_events row).
//
// Idempotency: every step is guarded by information_schema lookups per
// the repo migration iron rules — MySQL 8 has no ADD/DROP COLUMN IF
// [NOT] EXISTS.
var _ = AddMigrateRecord(
	"2026-08-20-pricing-comparison-logs-raw-pricing",
	func(db *gorm.DB) error {
		var colCount int
		if err := db.Raw(
			"SELECT COUNT(*) FROM information_schema.columns "+
				"WHERE table_schema = DATABASE() AND table_name = ? AND column_name = ?",
			"pricing_events", "shadow",
		).Scan(&colCount).Error; err != nil {
			return err
		}
		if colCount > 0 {
			if err := db.Exec("ALTER TABLE `pricing_events` DROP COLUMN `shadow`").Error; err != nil {
				return err
			}
		}

		for _, col := range []string{"pricing", "raw"} {
			if err := db.Raw(
				"SELECT COUNT(*) FROM information_schema.columns "+
					"WHERE table_schema = DATABASE() AND table_name = ? AND column_name = ?",
				"pricing_comparison_logs", col,
			).Scan(&colCount).Error; err != nil {
				return err
			}
			if colCount == 0 {
				if err := db.Exec(
					"ALTER TABLE `pricing_comparison_logs` ADD COLUMN `" + col + "` text",
				).Error; err != nil {
					return err
				}
			}
		}

		if err := db.Raw(
			"SELECT COUNT(*) FROM information_schema.columns "+
				"WHERE table_schema = DATABASE() AND table_name = ? AND column_name = ?",
			"pricing_comparison_logs", "event_id",
		).Scan(&colCount).Error; err != nil {
			return err
		}
		if colCount > 0 {
			if err := db.Exec(
				"ALTER TABLE `pricing_comparison_logs` DROP COLUMN `event_id`",
			).Error; err != nil {
				return err
			}
		}
		return nil
	},
)

// 2026-08-20-pricing-events-cost-timestamp-index
//
// The pricing_events archive worker scans purely by time:
//
//	WHERE cost_timestamp < now - retention ORDER BY id
//
// No existing index starts with cost_timestamp (PK is id; the others
// lead with cost_id / status / merged_cost_id), so that scan would be a
// full table scan that degrades linearly with metering volume. This
// single-column index serves the archive range scan directly.
//
// Idempotency: information_schema guard per the repo migration iron
// rules (MySQL 8 has no CREATE INDEX IF NOT EXISTS).
var _ = AddMigrateRecord(
	"2026-08-20-pricing-events-cost-timestamp-index",
	func(db *gorm.DB) error {
		var idxCount int
		if err := db.Raw(
			"SELECT COUNT(*) FROM information_schema.statistics "+
				"WHERE table_schema = DATABASE() AND table_name = ? AND index_name = ?",
			"pricing_events", "idx_cost_timestamp",
		).Scan(&idxCount).Error; err != nil {
			return err
		}
		if idxCount == 0 {
			if err := db.Exec(
				"CREATE INDEX `idx_cost_timestamp` ON `pricing_events` (`cost_timestamp`)",
			).Error; err != nil {
				return err
			}
		}
		return nil
	},
)

// 2026-08-27-agentic-plan-v2
//
// Agentic Plan V2 billing (design doc:
// https://pingcap.feishu.cn/wiki/GHt2wTPPuiG2Y9k55F2cTIjDn3b):
//
//   - free_cap_accumulators: the Stage-2 free_cap usage adjuster's
//     exemption ledger (uk_acc on policy/tenant/window/cluster).
//     Dimension columns are NOT NULL DEFAULT ” — NULLs
//     would not dedupe under MySQL unique indexes and the ledger would
//     silently fragment (see the entity comment). The policy identity
//     keys the ledger from inside tenant_usages.usage_expression
//     (free_cap dialect, key "policy_id") — no tenant_usages column:
//     the expression IS the engine's own dialect, and the framework
//     contract stays usage_type + usage_expression.
//
//   - worker_metering_records: the generic trigger/watermark ledger
//     for derived-fee workers (worker_type + tenant + hour, PK cost_id
//     deterministic). The agentic worker's catch-up watermark was a
//     MAX scan over metering_events filtered by unit — metering_events
//     has no (tenant_id, unit) index, so every tick walked a big
//     tenant's full event history backwards. The watermark is now a
//     one-row seek on this table's idx_worker_tenant_time, written
//     only after the hour's trigger pair is fully enqueued
//     (mark-after-pair: a torn enqueue is retried whole; the events'
//     uk_cost_id absorbs the duplicates).
//
// No Active/Idle settlement journal: the usage adjuster is
// compute-only (clamp evidence persists in the pricing event's
// Detail map), and support_plan_metering_records is untouched — its
// readers (AR table, support plan continuity, Lake fee) only ever
// look at their own unit families. The plan whitelist is oneconfig
// (agentic-plan-tenant-list).
//
// Idempotency per the repo migration iron rules: the table is new
// (AutoMigrate is a no-op when present, and fresh DBs build it via
// initSchema anyway).
var _ = AddMigrateRecord(
	"2026-08-27-agentic-plan-v2",
	func(db *gorm.DB) error {
		return db.AutoMigrate(&entity.FreeCapAccumulator{}, &entity.WorkerMeteringRecords{})
	},
)
