from __future__ import annotations

import sqlite3
import tempfile
import unittest
from pathlib import Path

from backend.database import Migration, MigrationError, MigrationRunner
from database import ReviewDatabase


class DatabaseMigrationTests(unittest.TestCase):
    def test_fresh_database_records_the_adopted_schema_once(self) -> None:
        with tempfile.TemporaryDirectory() as root:
            path = Path(root) / "review.db"
            database = ReviewDatabase(path)
            with database.connect() as connection:
                rows = connection.execute(
                    "SELECT version, name FROM schema_migrations"
                ).fetchall()
                self.assertEqual(
                    [(row["version"], row["name"]) for row in rows],
                    [
                        ("0001", "adopt_legacy_schema"),
                        ("0002", "create_job_runs"),
                        ("0003", "extend_llm_audit"),
                        ("0004", "add_mentor_note"),
                        ("0005", "create_account_switch_grants"),
                    ],
                )
                columns = {
                    str(row["name"])
                    for row in connection.execute(
                        "PRAGMA table_info(mentor_preferences)"
                    )
                }
                self.assertIn("note", columns)
            ReviewDatabase(path)
            with database.connect() as connection:
                count = connection.execute(
                    "SELECT COUNT(*) AS count FROM schema_migrations"
                ).fetchone()["count"]
            self.assertEqual(count, 5)

    def test_database_with_recorded_0004_and_note_column_starts_without_reapply(
        self,
    ) -> None:
        with tempfile.TemporaryDirectory() as root:
            path = Path(root) / "review.db"
            database = ReviewDatabase(path)
            with database.connect() as connection:
                note_rows = [
                    str(row["name"])
                    for row in connection.execute(
                        "PRAGMA table_info(mentor_preferences)"
                    )
                ]
            self.assertIn("note", note_rows)
            ReviewDatabase(path)
            with database.connect() as connection:
                count = connection.execute(
                    "SELECT COUNT(*) AS count FROM schema_migrations"
                ).fetchone()["count"]
            self.assertEqual(count, 5)

    def test_old_database_without_0004_upgrades_and_adds_note_column(self) -> None:
        with tempfile.TemporaryDirectory() as root:
            path = Path(root) / "review.db"
            database = ReviewDatabase(path)
            with database.connect() as connection:
                connection.execute(
                    "DELETE FROM schema_migrations WHERE version = '0004'"
                )
                connection.execute(
                    "ALTER TABLE mentor_preferences DROP COLUMN note"
                )
            ReviewDatabase(path)
            with database.connect() as connection:
                versions = {
                    str(row["version"])
                    for row in connection.execute(
                        "SELECT version FROM schema_migrations"
                    )
                }
                note_rows = [
                    str(row["name"])
                    for row in connection.execute(
                        "PRAGMA table_info(mentor_preferences)"
                    )
                ]
            self.assertEqual(versions, {"0001", "0002", "0003", "0004", "0005"})
            self.assertIn("note", note_rows)

    def test_database_with_unknown_migration_is_rejected(self) -> None:
        with tempfile.TemporaryDirectory() as root:
            path = Path(root) / "review.db"
            database = ReviewDatabase(path)
            with database.connect() as connection:
                connection.execute(
                    """
                    INSERT INTO schema_migrations
                        (version, name, checksum, applied_at)
                    VALUES ('9999', 'unknown_legacy', 'x', '2026-08-01T00:00:00+00:00')
                    """
                )
            with self.assertRaises(MigrationError):
                ReviewDatabase(path)

    def test_connection_factory_enables_required_pragmas(self) -> None:
        with tempfile.TemporaryDirectory() as root:
            database = ReviewDatabase(Path(root) / "review.db")
            with database.connect() as connection:
                self.assertEqual(connection.execute("PRAGMA foreign_keys").fetchone()[0], 1)
                self.assertEqual(connection.execute("PRAGMA journal_mode").fetchone()[0], "wal")
                self.assertEqual(connection.execute("PRAGMA busy_timeout").fetchone()[0], 20000)

    def test_failed_migration_rolls_back_and_is_not_recorded(self) -> None:
        connection = sqlite3.connect(":memory:")
        self.addCleanup(connection.close)
        connection.row_factory = sqlite3.Row

        def fail(conn: sqlite3.Connection) -> None:
            conn.execute("CREATE TABLE should_rollback (id INTEGER)")
            raise RuntimeError("stop")

        migration = Migration("9000", "failure", fail, "failure:v1")
        with self.assertRaises(MigrationError):
            MigrationRunner().apply(connection, (migration,))
        tables = {
            row["name"]
            for row in connection.execute(
                "SELECT name FROM sqlite_master WHERE type = 'table'"
            )
        }
        self.assertNotIn("should_rollback", tables)
        self.assertEqual(
            connection.execute("SELECT COUNT(*) FROM schema_migrations").fetchone()[0],
            0,
        )

    def test_applied_migration_checksum_is_immutable(self) -> None:
        connection = sqlite3.connect(":memory:")
        self.addCleanup(connection.close)
        connection.row_factory = sqlite3.Row
        first = Migration("9001", "example", lambda conn: None, "example:v1")
        changed = Migration("9001", "example", lambda conn: None, "example:v2")
        runner = MigrationRunner()
        runner.apply(connection, (first,))
        with self.assertRaises(MigrationError):
            runner.apply(connection, (changed,))


if __name__ == "__main__":
    unittest.main()
