from __future__ import annotations

import argparse
import importlib.util
import json
from pathlib import Path

import pytest

from jarvis_finance.storage.database import connect
from jarvis_finance.storage.migrations import apply_migrations


def _module():
    path = Path(__file__).parents[2] / "scripts" / "sprint16_household_onboarding.py"
    spec = importlib.util.spec_from_file_location("sprint16_household_onboarding", path)
    assert spec and spec.loader
    module = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(module)
    return module


def _raiffeisen_csv() -> str:
    lines = ["IBAN;Booked At;Text;Credit/Debit Amount;Balance;Valuta Date"]
    for index in range(69):
        is_interest = index == 68
        lines.append(
            ";".join(
                [
                    "SYN-REF-B-2222" if is_interest else "SYN-REF-A-1111",
                    "2026-07-28",
                    "Interest" if is_interest else f"Synthetic {index}",
                    "1.25" if is_interest else f"-{index + 1}.00",
                    "20.00" if is_interest else "1000.00",
                    "2026-07-28",
                ]
            )
        )
        if index < 57:
            lines.append(f";;Continuation {index}")
    return "\n".join(lines) + "\n"


def _visa_csv() -> str:
    lines = ["TransactionId,CardId,Date,Amount,Currency,MerchantName,StateType"]
    for index in range(12):
        reference = "SYN-CARD" if index < 10 else ""
        lines.append(f"T{index},{reference},2026-07-01,1.00,CHF,Synthetic,booked")
    return "\n".join(lines) + "\n"


def test_onboarding_is_copy_first_integrity_checked_private_manifest_bound_and_idempotent(
    tmp_path: Path, monkeypatch,
) -> None:
    module = _module()
    monkeypatch.setenv("JARVIS_FINANCE_FINGERPRINT_KEY", "synthetic-onboarding-key")
    database = tmp_path / "finance.sqlite3"
    conn = connect(database)
    apply_migrations(conn)
    conn.execute(
        "INSERT INTO platforms(platform_id,name,platform_type,created_at) VALUES ('p','Synthetic','bank','2026-01-01')"
    )
    for account_id in ("acct_raiffeisen", "acct_akb"):
        conn.execute(
            """INSERT INTO accounts(
                 account_id,platform_id,account_name,account_type,currency,performance_included,
                 portfolio_bucket,created_at)
               VALUES (?, 'p', ?, 'cash', 'CHF', 0, 'cash', '2026-01-01')""",
            (account_id, account_id),
        )
    conn.commit()
    conn.close()

    raiffeisen = tmp_path / "raiffeisen.csv"
    visa = tmp_path / "visa.csv"
    raiffeisen.write_text(_raiffeisen_csv())
    visa.write_text(_visa_csv())
    manifest = tmp_path / "authorization.json"
    manifest.write_text(
        json.dumps(
            {
                "contract": "s16-source-truth-v1",
                "raiffeisen": {
                    "physical_lines_including_header": 127,
                    "reference_a_bookings": 68,
                    "reference_b_bookings": 1,
                    "reference_a_suffix": "1111",
                    "reference_b_suffix": "2222",
                    "reference_a_provider_balance_chf": "1000.00",
                    "reference_b_provider_balance_chf": "20.00",
                    "reference_b_interest_chf": "1.25",
                    "chain_audit": {
                        "both_base_blocks": True,
                        "both_reference_a": True,
                        "both_transitions_correct": True,
                        "removal_breaks_chain": True,
                        "identical_provider_transaction_id": False,
                        "pending_booked_pair": False,
                    },
                },
                "visa": {"transactions": 12, "provider_reference_rows": 10, "fallback_role_rows": 2},
            }
        )
    )
    manifest.chmod(0o600)
    common = {
        "db": database,
        "authorization_manifest": manifest,
        "raiffeisen_csv": raiffeisen,
        "visa_csv": visa,
        "raiffeisen_account_id": "acct_raiffeisen",
        "akb_account_id": "acct_akb",
        "akb_source_reference": "SYN-AKB",
        "authorization": module.AUTHORIZATION,
    }
    dry = module.run(argparse.Namespace(**common, backup=None, confirm_onboarding=False))
    first = module.run(
        argparse.Namespace(**common, backup=tmp_path / "before-first.sqlite3", confirm_onboarding=True)
    )
    conn = connect(database)
    audits_before = conn.execute("SELECT COUNT(*) FROM audit_log").fetchone()[0]
    conn.close()
    retry = module.run(
        argparse.Namespace(**common, backup=tmp_path / "before-retry.sqlite3", confirm_onboarding=True)
    )
    conn = connect(database)
    audits_after = conn.execute("SELECT COUNT(*) FROM audit_log").fetchone()[0]
    mapping_count = conn.execute("SELECT COUNT(*) FROM household_account_source_mappings").fetchone()[0]
    integrity = conn.execute("PRAGMA integrity_check").fetchone()[0]
    roles = conn.execute(
        """SELECT account_type,currency,performance_included,portfolio_bucket
           FROM accounts WHERE account_id IN ('acct_s16_raiffeisen_extra','acct_s16_visa_liability')
           ORDER BY account_id"""
    ).fetchall()
    conn.close()

    assert dry["database_unchanged"] is True
    assert dry["raiffeisen"] == {
        "physical_lines_including_header": 127,
        "logical_bookings": 69,
        "reference_distribution": [68, 1],
    }
    assert first["result"]["status"] == "confirmed"
    assert first["result"]["backup_restore_verified"] is True
    assert (tmp_path / "before-first.sqlite3").stat().st_mode & 0o777 == 0o600
    assert retry["result"]["status"] == "unchanged" and retry["result"]["idempotent"] is True
    assert audits_after == audits_before and mapping_count == 5 and integrity == "ok"
    assert [tuple(row) for row in roles] == [
        ("cash", "CHF", 0, "cash"),
        ("credit_card_liability", "CHF", 0, "liability"),
    ]

    conn = connect(database)
    conn.execute("UPDATE accounts SET is_health_reserve=1 WHERE account_id='acct_s16_raiffeisen_extra'")
    conn.commit()
    with pytest.raises(RuntimeError, match="incompatible semantics"):
        module._ensure_account(
            conn,
            account_id="acct_s16_raiffeisen_extra",
            platform_id="p",
            name="Raiffeisen Mitglieder-Sparkonto •••• 2222",
            account_type="cash",
            bucket="cash",
            notes="Synthetic",
        )
    conn.execute("UPDATE accounts SET is_health_reserve=0 WHERE account_id='acct_s16_raiffeisen_extra'")
    conn.execute("UPDATE budget_accounts SET is_active=0 WHERE budget_account_id='bacc_s16_raiffeisen_b'")
    conn.commit()
    with pytest.raises(RuntimeError, match="incompatible semantics"):
        module._ensure_budget_account(
            conn,
            budget_id="bacc_s16_raiffeisen_b",
            account_id="acct_s16_raiffeisen_extra",
            name="Raiffeisen Mitglieder-Sparkonto •••• 2222",
            account_type="checking",
        )
    conn.close()
