"""
Tests for Phase 95: Data Migration Engine for Platform Upgrades.
"""

import json

import pytest


@pytest.fixture(autouse=True)
def _isolate(tmp_path, monkeypatch):
    """Redirect migration storage to a temp directory and reset state."""
    mig_dir = tmp_path / "migrations"
    mig_dir.mkdir(parents=True, exist_ok=True)

    import brynq.server as srv
    monkeypatch.setattr(srv, "BROKER_DIR", tmp_path)

    import brynq.migration_engine as mod
    monkeypatch.setattr(mod, "MIG_DIR", mig_dir)
    monkeypatch.setattr(mod, "REGISTRY_FILE", mig_dir / "registry.json")
    monkeypatch.setattr(mod, "STATE_FILE", mig_dir / "state.json")
    monkeypatch.setattr(mod, "HISTORY_FILE", mig_dir / "history.jsonl")
    mod._reset()


# ── 1. Register Migration ─────────────────────────────────────────────────


class TestRegisterMigration:
    def test_register_basic(self):
        from brynq.migration_engine import register_migration
        result = register_migration("001", "Add users table", lambda: None)
        assert result["ok"] is True
        assert result["version"] == "001"

    def test_register_with_down(self):
        from brynq.migration_engine import register_migration
        result = register_migration("001", "Add users", lambda: None, down_fn=lambda: None)
        assert result["ok"] is True

    def test_register_duplicate(self):
        from brynq.migration_engine import register_migration
        register_migration("001", "first", lambda: None)
        result = register_migration("001", "duplicate", lambda: None)
        assert "error" in result


# ── 2. Current Version ────────────────────────────────────────────────────


class TestCurrentVersion:
    def test_initial_version(self):
        from brynq.migration_engine import get_current_version
        assert get_current_version() is None

    def test_version_after_migrate(self):
        from brynq.migration_engine import register_migration, migrate_up, get_current_version
        register_migration("001", "first", lambda: None)
        migrate_up()
        assert get_current_version() == "001"


# ── 3. Pending Migrations ────────────────────────────────────────────────


class TestPendingMigrations:
    def test_no_migrations(self):
        from brynq.migration_engine import get_pending_migrations
        assert get_pending_migrations() == []

    def test_all_pending(self):
        from brynq.migration_engine import register_migration, get_pending_migrations
        register_migration("001", "first", lambda: None)
        register_migration("002", "second", lambda: None)
        pending = get_pending_migrations()
        assert len(pending) == 2
        assert pending[0]["version"] == "001"

    def test_some_applied(self):
        from brynq.migration_engine import register_migration, migrate_up, get_pending_migrations
        register_migration("001", "first", lambda: None)
        register_migration("002", "second", lambda: None)
        migrate_up(target_version="001")
        pending = get_pending_migrations()
        assert len(pending) == 1
        assert pending[0]["version"] == "002"


# ── 4. Migrate Up ─────────────────────────────────────────────────────────


class TestMigrateUp:
    def test_migrate_all(self):
        from brynq.migration_engine import register_migration, migrate_up, get_current_version
        log = []
        register_migration("001", "first", lambda: log.append("001"))
        register_migration("002", "second", lambda: log.append("002"))
        result = migrate_up()
        assert result["ok"] is True
        assert len(result["applied"]) == 2
        assert log == ["001", "002"]
        assert get_current_version() == "002"

    def test_migrate_to_target(self):
        from brynq.migration_engine import register_migration, migrate_up, get_current_version
        register_migration("001", "first", lambda: None)
        register_migration("002", "second", lambda: None)
        register_migration("003", "third", lambda: None)
        migrate_up(target_version="002")
        assert get_current_version() == "002"

    def test_migrate_idempotent(self):
        from brynq.migration_engine import register_migration, migrate_up
        count = [0]
        register_migration("001", "first", lambda: count.__setitem__(0, count[0] + 1))
        migrate_up()
        migrate_up()
        assert count[0] == 1

    def test_migrate_failure_stops(self):
        from brynq.migration_engine import register_migration, migrate_up, get_current_version
        register_migration("001", "good", lambda: None)
        register_migration("002", "bad", lambda: (_ for _ in ()).throw(RuntimeError("boom")))
        register_migration("003", "skipped", lambda: None)
        result = migrate_up()
        failed = [r for r in result["applied"] if r["status"] == "failed"]
        assert len(failed) == 1
        assert get_current_version() == "001"

    def test_empty_migrate(self):
        from brynq.migration_engine import migrate_up
        result = migrate_up()
        assert result["applied"] == []


# ── 5. Migrate Down ───────────────────────────────────────────────────────


class TestMigrateDown:
    def test_rollback_one(self):
        from brynq.migration_engine import register_migration, migrate_up, migrate_down, get_current_version
        log = []
        register_migration("001", "first", lambda: log.append("up1"), down_fn=lambda: log.append("down1"))
        register_migration("002", "second", lambda: log.append("up2"), down_fn=lambda: log.append("down2"))
        migrate_up()
        migrate_down("001")
        assert get_current_version() == "001"
        assert "down2" in log

    def test_rollback_all(self):
        from brynq.migration_engine import register_migration, migrate_up, migrate_down, get_current_version
        register_migration("001", "first", lambda: None, down_fn=lambda: None)
        register_migration("002", "second", lambda: None, down_fn=lambda: None)
        migrate_up()
        migrate_down("")
        assert get_current_version() is None

    def test_rollback_no_down_fn(self):
        from brynq.migration_engine import register_migration, migrate_up, migrate_down
        register_migration("001", "first", lambda: None)
        register_migration("002", "no down", lambda: None)
        migrate_up()
        result = migrate_down("001")
        failed = [r for r in result["rolled_back"] if r["status"] == "failed"]
        assert len(failed) == 1

    def test_rollback_failure_stops(self):
        from brynq.migration_engine import register_migration, migrate_up, migrate_down, get_current_version
        register_migration("001", "ok", lambda: None, down_fn=lambda: None)
        register_migration("002", "fail down", lambda: None, down_fn=lambda: (_ for _ in ()).throw(RuntimeError("nope")))
        register_migration("003", "ok too", lambda: None, down_fn=lambda: None)
        migrate_up()
        migrate_down("001")
        # 003 should rollback fine, 002 should fail, stopping before 001
        # Current version should still be above 001
        assert get_current_version() is not None


# ── 6. Migration History ──────────────────────────────────────────────────


class TestMigrationHistory:
    def test_empty_history(self):
        from brynq.migration_engine import get_migration_history
        assert get_migration_history() == []

    def test_history_after_migrate(self):
        from brynq.migration_engine import register_migration, migrate_up, get_migration_history
        register_migration("001", "first", lambda: None)
        migrate_up()
        history = get_migration_history()
        assert len(history) == 1
        assert history[0]["version"] == "001"
        assert history[0]["direction"] == "up"

    def test_history_includes_rollback(self):
        from brynq.migration_engine import register_migration, migrate_up, migrate_down, get_migration_history
        register_migration("001", "first", lambda: None, down_fn=lambda: None)
        migrate_up()
        migrate_down("")
        history = get_migration_history()
        assert len(history) == 2
        assert history[1]["direction"] == "down"


# ── 7. Migration Status ──────────────────────────────────────────────────


class TestMigrationStatus:
    def test_initial_status(self):
        from brynq.migration_engine import get_migration_status
        status = get_migration_status()
        assert status["current_version"] is None
        assert status["applied_count"] == 0
        assert status["pending_count"] == 0

    def test_status_after_register(self):
        from brynq.migration_engine import register_migration, get_migration_status
        register_migration("001", "first", lambda: None)
        status = get_migration_status()
        assert status["pending_count"] == 1
        assert status["total_registered"] == 1

    def test_status_after_migrate(self):
        from brynq.migration_engine import register_migration, migrate_up, get_migration_status
        register_migration("001", "first", lambda: None)
        register_migration("002", "second", lambda: None)
        migrate_up(target_version="001")
        status = get_migration_status()
        assert status["current_version"] == "001"
        assert status["applied_count"] == 1
        assert status["pending_count"] == 1
        assert status["last_applied"] is not None
