from __future__ import annotations

from pathlib import Path

import pytest

from jarvis_finance.cli.main import main
from jarvis_finance.crypto.assets import create_crypto_asset
from jarvis_finance.runtime.backup import backup_runtime_db, restore_runtime_db, verify_backup
from jarvis_finance.storage.database import connect
from jarvis_finance.storage.migrations import apply_migrations


def _seed_db(db_path: Path, marker: str = "demo") -> None:
    conn = connect(db_path)
    apply_migrations(conn)
    create_crypto_asset(conn, coin_name=f"Synthetic {marker}", symbol=marker.upper()[:6], coingecko_id=f"synthetic-{marker}")
    conn.close()


def test_backup_runtime_db_creates_backup_outside_repo(tmp_path: Path) -> None:
    repo = tmp_path / "repo"
    repo.mkdir()
    runtime = tmp_path / "runtime"
    db = runtime / "data" / "finance.sqlite3"
    backups = runtime / "backups"
    _seed_db(db)

    result = backup_runtime_db(db_path=db, backups_dir=backups, repo_root=repo, timestamp="20260101T000000Z")

    assert result.backup_file.exists()
    assert result.checksum_file.exists()
    assert result.backup_file.parent == backups.resolve()
    assert not result.backup_file.resolve().is_relative_to(repo.resolve())
    assert len(result.sha256) == 64


def test_verify_backup_checks_checksum(tmp_path: Path) -> None:
    repo = tmp_path / "repo"
    repo.mkdir()
    runtime = tmp_path / "runtime"
    db = runtime / "data" / "finance.sqlite3"
    _seed_db(db)
    backup = backup_runtime_db(db_path=db, backups_dir=runtime / "backups", repo_root=repo)

    verified = verify_backup(backup_file=backup.backup_file, repo_root=repo)
    assert verified.exists is True
    assert verified.checksum_ok is True

    backup.backup_file.write_bytes(backup.backup_file.read_bytes() + b"tamper")
    tampered = verify_backup(backup_file=backup.backup_file, repo_root=repo)
    assert tampered.checksum_ok is False


def test_restore_runtime_db_requires_yes_and_restores_test_db(tmp_path: Path) -> None:
    repo = tmp_path / "repo"
    repo.mkdir()
    runtime = tmp_path / "runtime"
    db = runtime / "data" / "finance.sqlite3"
    _seed_db(db, marker="before")
    backup = backup_runtime_db(db_path=db, backups_dir=runtime / "backups", repo_root=repo)

    conn = connect(db)
    conn.execute("DELETE FROM crypto_assets")
    conn.commit()
    conn.close()

    with pytest.raises(PermissionError):
        restore_runtime_db(backup_file=backup.backup_file, db_path=db, repo_root=repo, yes=False)

    result = restore_runtime_db(backup_file=backup.backup_file, db_path=db, repo_root=repo, yes=True)
    assert result.verified is True
    conn = connect(db)
    count = conn.execute("SELECT COUNT(*) AS n FROM crypto_assets").fetchone()["n"]
    conn.close()
    assert count == 1


def test_restore_backup_to_separate_test_db_does_not_modify_productive_db(tmp_path: Path) -> None:
    repo = tmp_path / "repo"
    repo.mkdir()
    runtime = tmp_path / "runtime"
    productive_db = runtime / "data" / "finance.sqlite3"
    restore_db = runtime / "restore-tests" / "finance-restored.sqlite3"
    _seed_db(productive_db, marker="prod")
    backup = backup_runtime_db(db_path=productive_db, backups_dir=runtime / "backups", repo_root=repo)

    conn = connect(productive_db)
    create_crypto_asset(conn, coin_name="Synthetic After Backup", symbol="AFTER", coingecko_id="synthetic-after")
    conn.close()

    result = restore_runtime_db(backup_file=backup.backup_file, db_path=restore_db, repo_root=repo, yes=True)
    assert result.restored_to == restore_db.resolve()

    prod_conn = connect(productive_db)
    restored_conn = connect(restore_db)
    prod_count = prod_conn.execute("SELECT COUNT(*) AS n FROM crypto_assets").fetchone()["n"]
    restored_count = restored_conn.execute("SELECT COUNT(*) AS n FROM crypto_assets").fetchone()["n"]
    prod_conn.close()
    restored_conn.close()
    assert prod_count == 2
    assert restored_count == 1


def test_backup_cli_verify_and_restore(monkeypatch, tmp_path: Path, capsys) -> None:
    runtime = tmp_path / "runtime"
    monkeypatch.setenv("JARVIS_FINANCE_RUNTIME_DIR", str(runtime))
    db = runtime / "data" / "finance.sqlite3"
    _seed_db(db)

    backup_rc = main(["backup-runtime-db"])
    backup_out = capsys.readouterr().out
    assert backup_rc == 0
    assert "RUNTIME_DB_BACKUP" in backup_out
    backup_file = next(part.split("=", 1)[1] for part in backup_out.split() if part.startswith("backup_file="))
    assert str(runtime / "backups") in backup_file

    verify_rc = main(["verify-backup", "--file", backup_file])
    verify_out = capsys.readouterr().out
    assert verify_rc == 0
    assert "checksum_ok=true" in verify_out

    conn = connect(db)
    conn.execute("DELETE FROM crypto_assets")
    conn.commit()
    conn.close()
    restore_rc = main(["restore-runtime-db", "--from", backup_file, "--yes"])
    restore_out = capsys.readouterr().out
    assert restore_rc == 0
    assert "RUNTIME_DB_RESTORE" in restore_out

    conn = connect(db)
    count = conn.execute("SELECT COUNT(*) AS n FROM crypto_assets").fetchone()["n"]
    conn.close()
    assert count == 1
