package migrations

import (
	"context"
	"database/sql"
	"fmt"

	"github.com/pressly/goose/v3"
)

func init() {
	goose.AddMigrationContext(upRFC3339Time, downRFC3339Time)
}

// upRFC3339Time переводит хранение временных меток на RFC 3339 (UTC, суффикс Z,
// напр. «2006-01-02T15:04:05Z») и убирает `DEFAULT (datetime('now'))` со всех
// колонок: время отныне пишет только приложение (store.Now/FormatTime) — единая
// точка генерации, fail-loud при забытой вставке (NOT NULL без DEFAULT).
//
// SQLite не умеет ALTER COLUMN DROP DEFAULT, поэтому перестраиваем 7 таблиц по
// образцу миграции 0006 (FK включены): создаём *_new без DEFAULT на метках,
// копируем данные с переформатированием меток через strftime (id и связи НЕ
// меняются — момент времени сохраняется, ULID-корреляция цела), удаляем старые
// (дети первыми), переименовываем (SQLite ≥3.25 переписывает REFERENCES),
// пересоздаём индексы и проверяем целостность FK.
func upRFC3339Time(ctx context.Context, tx *sql.Tx) error {
	return rebuildTimeColumns(ctx, tx, "", rfc3339Expr)
}

// downRFC3339Time возвращает прежний формат меток (`YYYY-MM-DD HH:MM:SS`) и
// `DEFAULT (datetime('now'))` на колонках. Обратима без потерь: strftime
// разбирает RFC 3339 (T/Z) и момент времени сохраняется.
func downRFC3339Time(ctx context.Context, tx *sql.Tx) error {
	return rebuildTimeColumns(ctx, tx, " DEFAULT (datetime('now'))", sqliteExpr)
}

// rfc3339Expr / sqliteExpr — SQL-выражение переформатирования метки col в
// целевой формат (strftime корректно разбирает и пробел-, и T/Z-форму на входе).
func rfc3339Expr(col string) string {
	return fmt.Sprintf("strftime('%%Y-%%m-%%dT%%H:%%M:%%SZ', %s)", col)
}

func sqliteExpr(col string) string {
	return fmt.Sprintf("strftime('%%Y-%%m-%%d %%H:%%M:%%S', %s)", col)
}

// rebuildTimeColumns перестраивает все таблицы с временными метками: tsDefault —
// суффикс DEFAULT для колонок created_at/updated_at ("" — без дефолта); ts —
// выражение переформатирования метки при копировании.
func rebuildTimeColumns(ctx context.Context, tx *sql.Tx, tsDefault string, ts func(string) string) error {
	stmts := make([]string, 0, 28)
	stmts = append(stmts, createTimeTables(tsDefault)...)
	stmts = append(stmts, copyTimeTables(ts)...)
	stmts = append(stmts, dropRenameReindex...)
	for _, stmt := range stmts {
		if _, err := tx.ExecContext(ctx, stmt); err != nil {
			return fmt.Errorf("rfc3339 time migration: %q: %w", stmt, err)
		}
	}
	return checkForeignKeys(ctx, tx)
}

// createTimeTables — CREATE *_new для всех таблиц; d подставляет DEFAULT на
// метки created_at/updated_at (прочие DEFAULT — ”, 0, '[]', 1 — неизменны).
func createTimeTables(d string) []string {
	return []string{
		fmt.Sprintf(`CREATE TABLE download_new (
    id                TEXT    PRIMARY KEY,
    source_type       TEXT    NOT NULL,
    source_ref        TEXT    NOT NULL,
    display_name      TEXT    NOT NULL DEFAULT '',
    context           TEXT    NOT NULL DEFAULT '',
    state             TEXT    NOT NULL,
    error_code        TEXT,
    error_msg         TEXT,
    source_miss_count INTEGER NOT NULL DEFAULT 0,
    source_added_at   TEXT,
    created_at        TEXT    NOT NULL%[1]s,
    updated_at        TEXT    NOT NULL%[1]s
)`, d),
		fmt.Sprintf(`CREATE TABLE download_infohash_new (
    download_id TEXT NOT NULL REFERENCES download_new (id) ON DELETE CASCADE,
    infohash    TEXT NOT NULL,
    kind        TEXT NOT NULL,
    created_at  TEXT NOT NULL%[1]s,
    PRIMARY KEY (infohash, download_id)
)`, d),
		fmt.Sprintf(`CREATE TABLE recognition_new (
    id             TEXT    PRIMARY KEY,
    download_id    TEXT    NOT NULL REFERENCES download_new (id) ON DELETE CASCADE,
    attempt_no     INTEGER NOT NULL DEFAULT 1,
    is_current     INTEGER NOT NULL DEFAULT 1,
    media_type     TEXT,
    title          TEXT,
    original_title TEXT,
    year           INTEGER,
    provider       TEXT,
    provider_id    TEXT,
    confidence     REAL,
    reasons        TEXT    NOT NULL DEFAULT '[]',
    raw_llm        TEXT,
    plan           TEXT,
    created_at     TEXT    NOT NULL%[1]s
)`, d),
		fmt.Sprintf(`CREATE TABLE hint_new (
    id          TEXT PRIMARY KEY,
    download_id TEXT NOT NULL REFERENCES download_new (id) ON DELETE CASCADE,
    text        TEXT NOT NULL,
    created_at  TEXT NOT NULL%[1]s
)`, d),
		fmt.Sprintf(`CREATE TABLE override_new (
    id          TEXT PRIMARY KEY,
    download_id TEXT NOT NULL REFERENCES download_new (id) ON DELETE CASCADE,
    field       TEXT NOT NULL,
    value       TEXT NOT NULL,
    created_at  TEXT NOT NULL%[1]s,
    UNIQUE (download_id, field)
)`, d),
		fmt.Sprintf(`CREATE TABLE metadata_candidate_new (
    id             TEXT    PRIMARY KEY,
    recognition_id TEXT    NOT NULL REFERENCES recognition_new (id) ON DELETE CASCADE,
    provider       TEXT    NOT NULL,
    provider_id    TEXT    NOT NULL,
    title          TEXT,
    year           INTEGER,
    chosen         INTEGER NOT NULL DEFAULT 0,
    url            TEXT,
    created_at     TEXT    NOT NULL%[1]s
)`, d),
		fmt.Sprintf(`CREATE TABLE file_link_new (
    id             TEXT PRIMARY KEY,
    download_id    TEXT NOT NULL REFERENCES download_new (id) ON DELETE CASCADE,
    apply_batch_id TEXT NOT NULL,
    src_path       TEXT NOT NULL,
    dst_path       TEXT NOT NULL,
    kind           TEXT NOT NULL,
    status         TEXT NOT NULL,
    size           INTEGER NOT NULL DEFAULT 0,
    created_at     TEXT NOT NULL%[1]s
)`, d),
	}
}

// copyTimeTables — INSERT ... SELECT в *_new с переформатированием меток через
// ts(col). Списки колонок явные; strftime(NULL) = NULL — nullable source_added_at
// корректно щадится.
func copyTimeTables(ts func(string) string) []string {
	return []string{
		fmt.Sprintf(`INSERT INTO download_new
  (id, source_type, source_ref, display_name, context, state, error_code,
   error_msg, source_miss_count, source_added_at, created_at, updated_at)
SELECT id, source_type, source_ref, display_name, context, state, error_code,
       error_msg, source_miss_count, %s, %s, %s
FROM download`, ts("source_added_at"), ts("created_at"), ts("updated_at")),
		fmt.Sprintf(`INSERT INTO download_infohash_new (download_id, infohash, kind, created_at)
SELECT download_id, infohash, kind, %s FROM download_infohash`, ts("created_at")),
		fmt.Sprintf(`INSERT INTO recognition_new
  (id, download_id, attempt_no, is_current, media_type, title, original_title,
   year, provider, provider_id, confidence, reasons, raw_llm, plan, created_at)
SELECT id, download_id, attempt_no, is_current, media_type, title, original_title,
       year, provider, provider_id, confidence, reasons, raw_llm, plan, %s
FROM recognition`, ts("created_at")),
		fmt.Sprintf(`INSERT INTO hint_new (id, download_id, text, created_at)
SELECT id, download_id, text, %s FROM hint`, ts("created_at")),
		fmt.Sprintf(`INSERT INTO override_new (id, download_id, field, value, created_at)
SELECT id, download_id, field, value, %s FROM override`, ts("created_at")),
		fmt.Sprintf(`INSERT INTO metadata_candidate_new
  (id, recognition_id, provider, provider_id, title, year, chosen, url, created_at)
SELECT id, recognition_id, provider, provider_id, title, year, chosen, url, %s
FROM metadata_candidate`, ts("created_at")),
		fmt.Sprintf(`INSERT INTO file_link_new
  (id, download_id, apply_batch_id, src_path, dst_path, kind, status, size, created_at)
SELECT id, download_id, apply_batch_id, src_path, dst_path, kind, status, size, %s
FROM file_link`, ts("created_at")),
	}
}

// dropRenameReindex — удаление старых таблиц (дети первыми, FK включены),
// переименование *_new и пересоздание индексов (канонический набор из 0006).
var dropRenameReindex = []string{
	`DROP TABLE file_link`,
	`DROP TABLE metadata_candidate`,
	`DROP TABLE override`,
	`DROP TABLE hint`,
	`DROP TABLE recognition`,
	`DROP TABLE download_infohash`,
	`DROP TABLE download`,
	`ALTER TABLE download_new RENAME TO download`,
	`ALTER TABLE download_infohash_new RENAME TO download_infohash`,
	`ALTER TABLE recognition_new RENAME TO recognition`,
	`ALTER TABLE hint_new RENAME TO hint`,
	`ALTER TABLE override_new RENAME TO override`,
	`ALTER TABLE metadata_candidate_new RENAME TO metadata_candidate`,
	`ALTER TABLE file_link_new RENAME TO file_link`,
	`CREATE INDEX idx_download_state ON download (state)`,
	`CREATE INDEX idx_download_infohash_download ON download_infohash (download_id)`,
	`CREATE INDEX idx_recognition_download ON recognition (download_id)`,
	`CREATE INDEX idx_hint_download ON hint (download_id)`,
	`CREATE INDEX idx_candidate_recognition ON metadata_candidate (recognition_id)`,
	`CREATE INDEX idx_file_link_download ON file_link (download_id)`,
	`CREATE INDEX idx_file_link_batch ON file_link (apply_batch_id)`,
}
