using System.Data;
using Dapper;

namespace QuantEngine.Infrastructure.Data
{
    public class DbMigrator
    {
        private readonly IDbConnectionFactory _connectionFactory;

        public DbMigrator(IDbConnectionFactory connectionFactory)
        {
            _connectionFactory = connectionFactory;
        }

        public void Migrate()
        {
            using var conn = _connectionFactory.CreateConnection();
            conn.Open();

            // Create schema if not exists
            conn.Execute("CREATE SCHEMA IF NOT EXISTS quantengine;");

            // 0. kis_tokens
            conn.Execute(@"
                CREATE TABLE IF NOT EXISTS kis_tokens (
                    account TEXT PRIMARY KEY,
                    access_token TEXT NOT NULL,
                    expires_at TEXT NOT NULL,
                    updated_at TEXT NOT NULL
                );
            ");

            // 1. collection_runs
            conn.Execute(@"
                CREATE TABLE IF NOT EXISTS collection_runs (
                    run_id TEXT PRIMARY KEY,
                    collector_name TEXT NOT NULL,
                    started_at TEXT NOT NULL,
                    finished_at TEXT,
                    status TEXT NOT NULL,
                    input_source TEXT,
                    output_json_path TEXT,
                    output_db_path TEXT,
                    notes TEXT,
                    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
                );
            ");

            // 2. collection_snapshots
            conn.Execute(@"
                CREATE TABLE IF NOT EXISTS collection_snapshots (
                    run_id TEXT NOT NULL,
                    dataset_name TEXT NOT NULL,
                    ticker TEXT NOT NULL,
                    name TEXT,
                    sector TEXT,
                    as_of_date TEXT,
                    source_priority TEXT,
                    source_status TEXT,
                    payload_json TEXT NOT NULL,
                    provenance_json TEXT NOT NULL,
                    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                    PRIMARY KEY (run_id, dataset_name, ticker)
                );
                CREATE INDEX IF NOT EXISTS idx_collection_snapshots_ticker_time ON collection_snapshots(ticker, created_at DESC);
            ");

            // 3. collection_source_errors
            conn.Execute(@"
                CREATE TABLE IF NOT EXISTS collection_source_errors (
                    run_id TEXT NOT NULL,
                    ticker TEXT,
                    source_name TEXT NOT NULL,
                    error_kind TEXT NOT NULL,
                    error_message TEXT NOT NULL,
                    payload_json TEXT,
                    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
                );
                CREATE INDEX IF NOT EXISTS idx_collection_source_errors_run ON collection_source_errors(run_id, source_name);
            ");

            // 4. settings
            conn.Execute(@"
                CREATE TABLE IF NOT EXISTS settings (
                    ordinal INT NOT NULL,
                    key TEXT PRIMARY KEY,
                    value_json TEXT NOT NULL,
                    note TEXT NOT NULL DEFAULT '',
                    updated_at TEXT NOT NULL
                );
            ");

            // 5. account_snapshot
            conn.Execute(@"
                CREATE TABLE IF NOT EXISTS account_snapshot (
                    ordinal INT NOT NULL,
                    row_json TEXT NOT NULL,
                    captured_at TEXT NOT NULL DEFAULT '',
                    account TEXT NOT NULL DEFAULT '',
                    account_type TEXT NOT NULL DEFAULT '',
                    ticker TEXT NOT NULL DEFAULT '',
                    name TEXT NOT NULL DEFAULT '',
                    parse_status TEXT NOT NULL DEFAULT '',
                    user_confirmed TEXT NOT NULL DEFAULT '',
                    updated_at TEXT NOT NULL
                );
                CREATE INDEX IF NOT EXISTS idx_account_snapshot_captured_at ON account_snapshot(captured_at);
                CREATE INDEX IF NOT EXISTS idx_account_snapshot_ticker ON account_snapshot(ticker);
            ");

            // 6. workspace_meta
            conn.Execute(@"
                CREATE TABLE IF NOT EXISTS workspace_meta (
                    key TEXT PRIMARY KEY,
                    value_json TEXT NOT NULL
                );
            ");

            // 7. workspace_change_log
            conn.Execute(@"
                CREATE TABLE IF NOT EXISTS workspace_change_log (
                    id SERIAL PRIMARY KEY,
                    domain TEXT NOT NULL,
                    action TEXT NOT NULL,
                    target_ref TEXT NOT NULL DEFAULT '',
                    actor TEXT NOT NULL DEFAULT 'system',
                    note TEXT NOT NULL DEFAULT '',
                    before_json TEXT NOT NULL DEFAULT 'null',
                    after_json TEXT NOT NULL DEFAULT 'null',
                    created_at TEXT NOT NULL
                );
            ");

            // 8. workspace_approval_v2
            conn.Execute(@"
                CREATE TABLE IF NOT EXISTS workspace_approval_v2 (
                    domain TEXT NOT NULL,
                    target_ref TEXT NOT NULL DEFAULT '*',
                    status TEXT NOT NULL,
                    approved_by TEXT NOT NULL DEFAULT '',
                    approved_at TEXT NOT NULL DEFAULT '',
                    note TEXT NOT NULL DEFAULT '',
                    updated_at TEXT NOT NULL,
                    PRIMARY KEY (domain, target_ref)
                );
            ");

            // 9. workspace_lock
            conn.Execute(@"
                CREATE TABLE IF NOT EXISTS workspace_lock (
                    domain TEXT NOT NULL,
                    target_ref TEXT NOT NULL DEFAULT '',
                    locked_by TEXT NOT NULL DEFAULT '',
                    reason TEXT NOT NULL DEFAULT '',
                    locked_at TEXT NOT NULL,
                    PRIMARY KEY (domain, target_ref)
                );
            ");

            // 10. engine_history schema and tables
            conn.Execute(@"
                CREATE SCHEMA IF NOT EXISTS engine_history;
            ");
            conn.Execute(@"
                CREATE TABLE IF NOT EXISTS engine_history.market_raw_history (
                    id BIGSERIAL PRIMARY KEY,
                    source_id TEXT NOT NULL,
                    observed_at TEXT NOT NULL,
                    source_name TEXT NOT NULL,
                    instrument_id TEXT NOT NULL,
                    field_name TEXT NOT NULL,
                    field_value TEXT NOT NULL,
                    unit TEXT NOT NULL,
                    provenance JSONB NOT NULL DEFAULT '{}'::jsonb,
                    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
                );
                CREATE INDEX IF NOT EXISTS idx_market_raw_history_created_at ON engine_history.market_raw_history (created_at DESC);
            ");
            conn.Execute(@"
                CREATE TABLE IF NOT EXISTS engine_history.factor_version_history (
                    id BIGSERIAL PRIMARY KEY,
                    factor_id TEXT NOT NULL,
                    factor_version TEXT NOT NULL,
                    effective_from TEXT NOT NULL,
                    effective_to TEXT NOT NULL,
                    formula_id TEXT NOT NULL,
                    source_version TEXT NOT NULL,
                    provenance JSONB NOT NULL DEFAULT '{}'::jsonb,
                    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
                );
                CREATE INDEX IF NOT EXISTS idx_factor_version_history_created_at ON engine_history.factor_version_history (created_at DESC);
            ");
            conn.Execute(@"
                CREATE TABLE IF NOT EXISTS engine_history.factor_output_history (
                    id BIGSERIAL PRIMARY KEY,
                    factor_output_id TEXT NOT NULL,
                    observed_at TEXT NOT NULL,
                    factor_id TEXT NOT NULL,
                    factor_version TEXT NOT NULL,
                    output_value TEXT NOT NULL,
                    output_gate TEXT NOT NULL,
                    source_version TEXT NOT NULL,
                    provenance JSONB NOT NULL DEFAULT '{}'::jsonb,
                    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
                );
                CREATE INDEX IF NOT EXISTS idx_factor_output_history_created_at ON engine_history.factor_output_history (created_at DESC);
            ");
            conn.Execute(@"
                CREATE TABLE IF NOT EXISTS engine_history.decision_result_history (
                    id BIGSERIAL PRIMARY KEY,
                    decision_id TEXT NOT NULL,
                    decided_at TEXT NOT NULL,
                    instrument_id TEXT NOT NULL,
                    action TEXT NOT NULL,
                    gate TEXT NOT NULL,
                    score TEXT NOT NULL,
                    source_version TEXT NOT NULL,
                    provenance JSONB NOT NULL DEFAULT '{}'::jsonb,
                    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
                );
                CREATE INDEX IF NOT EXISTS idx_decision_result_history_created_at ON engine_history.decision_result_history (created_at DESC);
            ");
            conn.Execute(@"
                CREATE TABLE IF NOT EXISTS engine_history.market_vs_engine_gap_history (
                    id BIGSERIAL PRIMARY KEY,
                    gap_id TEXT NOT NULL,
                    observed_at TEXT NOT NULL,
                    instrument_id TEXT NOT NULL,
                    metric_name TEXT NOT NULL,
                    market_value TEXT NOT NULL,
                    engine_value TEXT NOT NULL,
                    gap_value TEXT NOT NULL,
                    gap_pct TEXT NOT NULL,
                    source_version TEXT NOT NULL,
                    provenance JSONB NOT NULL DEFAULT '{}'::jsonb,
                    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
                );
                CREATE INDEX IF NOT EXISTS idx_market_vs_engine_gap_history_created_at ON engine_history.market_vs_engine_gap_history (created_at DESC);
            ");
        }
    }
}
