from __future__ import annotations

import base64
from decimal import Decimal
from hashlib import sha256

import pytest

from jarvis_finance.imports.truewealth_tax_statement import (
    TrueWealthCash,
    TrueWealthPosition,
    TrueWealthStatement,
    parse_truewealth_text,
)
from jarvis_finance.services import truewealth_service as service
from jarvis_finance.storage.database import connect_memory
from jarvis_finance.storage.migrations import apply_migrations

TEST_ACCOUNT_ID = "account_truewealth_test"


def make_conn():
    conn = connect_memory()
    apply_migrations(conn)
    conn.execute(
        "INSERT INTO platforms(platform_id,name,platform_type,country,default_currency,is_active,created_at) VALUES('p','True Wealth','robo','CH','CHF',1,'2026-01-01')"
    )
    conn.execute(
        """INSERT INTO accounts(account_id,platform_id,account_name,account_type,currency,performance_included,is_active,created_at,portfolio_bucket)
           VALUES(?,'p','Manual Portfolio','managed_portfolio','CHF',0,1,'2026-01-01','truewealth')""",
        (TEST_ACCOUNT_ID,),
    )
    conn.execute(
        """INSERT INTO account_value_snapshots(snapshot_id,account_id,valuation_date,total_value_chf,currency,source_type,quality_status,notes,created_at)
           VALUES('legacy_manual',?,'2026-06-30','10000','CHF','manual_total_value','ok','historical','2026-06-30T12:00:00Z')""",
        (TEST_ACCOUNT_ID,),
    )
    conn.commit()
    return conn


def statement(raw: bytes, date: str = "2026-07-27", total: str = "12000") -> TrueWealthStatement:
    return TrueWealthStatement(
        statement_date=date,
        period_from="2026-01-01",
        period_to=date,
        source_total_chf=Decimal(total),
        positions=(
            TrueWealthPosition("position:CH0000000001", "Synthetic ETF A", "CH0000000001", Decimal("100"), "CHF", Decimal("50"), Decimal("5000")),
            TrueWealthPosition("position:US0000000002", "Synthetic ETF B", "US0000000002", Decimal("20"), "CHF", Decimal("200"), Decimal("4000")),
        ),
        cash=(TrueWealthCash("cash:CHF", "CHF", Decimal("3000"), None, Decimal("3000")),),
        file_sha256=sha256(raw).hexdigest(),
        page_count=7,
    )


def import_request(raw: bytes) -> dict[str, str]:
    return {
        "account_id": TEST_ACCOUNT_ID,
        "file_name": "official.pdf",
        "content_base64": base64.b64encode(raw).decode(),
    }


def confirm_import(conn, raw: bytes):
    request = import_request(raw)
    preview = service.preview_truewealth_import(conn, request)
    return service.confirm_truewealth_import(
        conn,
        {**request, "preview_id": preview["preview_id"], "confirmation_id": preview["confirmation_id"], "confirm": True},
    )


def test_text_parser_extracts_official_rows_without_inventing_values():
    text = """
True Wealth AG
Periode 01.01.2026 - 27.07.2026
Steuerauszug in CHF 27.07.2026
Total Steuerwert der A, B, DA-1 und USA-Werte am 27.07.2026
12’000 0 0 0
CHF (Synthetic Custodian AG)
27.07.2026 Steuerwert / Ertrag CHF 7’000.00 7’000.00 0.00
0000000 CH0000000001 - Synthetic ETF A, CH
27.07.2026 Bestand / Steuerwert / Ertrag 100.00 CHF 50.00 5’000.00 0.00
True Wealth AG
"""
    parsed = parse_truewealth_text(text, file_sha256="synthetic", page_count=1)
    assert parsed.statement_date == "2026-07-27"
    assert parsed.source_total_chf == Decimal("12000")
    assert parsed.positions[0].isin == "CH0000000001"
    assert parsed.cash[0].amount_original == Decimal("7000.00")
    assert parsed.components_total_chf == Decimal("12000.00")


def test_legacy_manual_value_is_never_labelled_as_official():
    conn = make_conn()
    summary = service.get_truewealth_summary(conn)
    assert summary["current_source_type"] == "manual_total_value"
    assert summary["current_is_manual_provisional"] is True
    assert "manuell" in summary["current_label"].lower()
    assert summary["allocation_performance_usable"] is False
    service.set_manual_truewealth_value_active(conn, "legacy_manual", {"active": False, "note": "reversible", "confirm": True})
    disabled = service.get_truewealth_summary(conn)
    assert disabled["current_value_chf"] is None
    assert any(row["snapshot_id"] == "legacy_manual" and row["is_active"] == 0 for row in disabled["history"])


def test_platform_spelling_without_space_and_empty_account_bootstrap(monkeypatch):
    conn = make_conn()
    conn.execute("UPDATE platforms SET name='TrueWealth' WHERE platform_id='p'")
    conn.execute("DELETE FROM account_value_snapshots")
    conn.commit()
    raw = b"%PDF-synthetic-bootstrap"
    monkeypatch.setattr(service, "parse_truewealth_tax_statement", lambda value: statement(value))
    preview = service.preview_truewealth_import(conn, import_request(raw))
    assert preview["reconciliation_status"] == "matched"
    assert service.get_truewealth_summary(conn)["current_value_chf"] is None


def test_official_confirm_is_idempotent_preserves_legacy_and_archives(monkeypatch, tmp_path):
    conn = make_conn()
    raw = b"%PDF-1.7 official-a"
    monkeypatch.setenv("JARVIS_FINANCE_RUNTIME_DIR", str(tmp_path))
    monkeypatch.setattr(service, "parse_truewealth_tax_statement", lambda value: statement(value))

    first = confirm_import(conn, raw)
    second = confirm_import(conn, raw)
    summary = service.get_truewealth_summary(conn)

    assert first["idempotent"] is False
    assert second["idempotent"] is True
    assert second["batch_id"] == first["batch_id"]
    assert summary["current_value_chf"] == "12000.00"
    assert summary["current_source_type"] == "truewealth_official_import"
    assert summary["allocation_performance_usable"] is True
    assert len(summary["positions"]) == 2
    assert conn.execute("SELECT COUNT(*) FROM account_value_snapshots WHERE snapshot_id='legacy_manual'").fetchone()[0] == 1
    assert conn.execute("SELECT COUNT(*) FROM truewealth_import_batches").fetchone()[0] == 1
    classification = conn.execute(
        """SELECT included,classification_role,decision_version
           FROM performance_scope_classifications WHERE account_id=?""",
        (TEST_ACCOUNT_ID,),
    ).fetchone()
    assert tuple(classification) == (1, "canonical_truewealth_total_value", "investment_performance_scope_v1")
    assert conn.execute(
        """SELECT COUNT(*) FROM audit_log
           WHERE entity_type='performance_scope_classification' AND entity_id=?""",
        (TEST_ACCOUNT_ID,),
    ).fetchone()[0] == 1
    archived = tmp_path / "imports" / "truewealth" / "archive" / f"{sha256(raw).hexdigest()}.pdf"
    assert archived.read_bytes() == raw
    assert archived.stat().st_mode & 0o777 == 0o600


def test_newer_manual_value_wins_without_changing_positions_and_can_be_reversed(monkeypatch, tmp_path):
    conn = make_conn()
    raw = b"%PDF-1.7 official-b"
    monkeypatch.setenv("JARVIS_FINANCE_RUNTIME_DIR", str(tmp_path))
    monkeypatch.setattr(service, "parse_truewealth_tax_statement", lambda value: statement(value))
    confirm_import(conn, raw)

    payload = {"total_value_chf": "12500", "valuation_at": "2026-07-28T08:00:00Z", "note": "portal view"}
    preview = service.preview_manual_truewealth_value(conn, payload)
    result = service.confirm_manual_truewealth_value(
        conn,
        {**payload, "preview_id": preview["preview_id"], "confirmation_id": preview["confirmation_id"], "confirm": True},
    )
    summary = service.get_truewealth_summary(conn)
    assert summary["current_value_chf"] == "12500.00"
    assert summary["current_is_manual_provisional"] is True
    assert summary["positions_snapshot_date"] == "2026-07-27"
    assert summary["mixed_as_of"] is True
    assert summary["allocation_performance_usable"] is False
    assert len(summary["positions"]) == 2

    service.set_manual_truewealth_value_active(conn, result["snapshot_id"], {"active": False, "note": "revert", "confirm": True})
    restored = service.get_truewealth_summary(conn)
    assert restored["current_value_chf"] == "12000.00"
    assert restored["current_source_type"] == "truewealth_official_import"
    assert any(row["snapshot_id"] == result["snapshot_id"] and row["is_active"] == 0 for row in restored["history"])
    service.set_manual_truewealth_value_active(conn, result["snapshot_id"], {"active": True, "note": "reactivate", "confirm": True})
    assert service.get_truewealth_summary(conn)["current_value_chf"] == "12500.00"


def test_older_official_import_cannot_displace_newer_manual(monkeypatch, tmp_path):
    conn = make_conn()
    monkeypatch.setenv("JARVIS_FINANCE_RUNTIME_DIR", str(tmp_path))
    newest_raw = b"%PDF-1.7 official-new"
    old_raw = b"%PDF-1.7 official-old"
    monkeypatch.setattr(service, "parse_truewealth_tax_statement", lambda value: statement(value, "2026-07-27" if value == newest_raw else "2026-07-20"))
    confirm_import(conn, newest_raw)
    payload = {"total_value_chf": "12500", "valuation_at": "2026-07-28T08:00:00Z", "note": "newer manual"}
    preview = service.preview_manual_truewealth_value(conn, payload)
    service.confirm_manual_truewealth_value(conn, {**payload, "preview_id": preview["preview_id"], "confirmation_id": preview["confirmation_id"], "confirm": True})
    confirm_import(conn, old_raw)
    summary = service.get_truewealth_summary(conn)
    assert summary["current_value_chf"] == "12500.00"
    assert summary["positions_snapshot_date"] == "2026-07-27"


def test_same_day_official_has_priority_over_manual(monkeypatch, tmp_path):
    conn = make_conn()
    monkeypatch.setenv("JARVIS_FINANCE_RUNTIME_DIR", str(tmp_path))
    payload = {"total_value_chf": "11500", "valuation_at": "2026-07-27T22:00:00Z", "note": "same day"}
    preview = service.preview_manual_truewealth_value(conn, payload)
    service.confirm_manual_truewealth_value(conn, {**payload, "preview_id": preview["preview_id"], "confirmation_id": preview["confirmation_id"], "confirm": True})
    raw = b"%PDF-1.7 same-day"
    monkeypatch.setattr(service, "parse_truewealth_tax_statement", lambda value: statement(value))
    confirm_import(conn, raw)
    assert service.get_truewealth_summary(conn)["current_source_type"] == "truewealth_official_import"


def test_preview_and_get_are_side_effect_free(monkeypatch):
    conn = make_conn()
    raw = b"%PDF-1.7 preview-only"
    monkeypatch.setattr(service, "parse_truewealth_tax_statement", lambda value: statement(value))
    before = conn.total_changes
    service.preview_truewealth_import(conn, import_request(raw))
    service.get_truewealth_summary(conn)
    assert conn.total_changes == before


def test_immutable_source_tables_reject_update_and_delete(monkeypatch, tmp_path):
    conn = make_conn()
    raw = b"%PDF-1.7 immutable"
    monkeypatch.setenv("JARVIS_FINANCE_RUNTIME_DIR", str(tmp_path))
    monkeypatch.setattr(service, "parse_truewealth_tax_statement", lambda value: statement(value))
    result = confirm_import(conn, raw)
    with pytest.raises(Exception, match="immutable"):
        conn.execute("UPDATE truewealth_snapshots SET source_total_chf='0' WHERE snapshot_id=?", (result["snapshot_id"],))
    with pytest.raises(Exception, match="cannot be deleted"):
        conn.execute("DELETE FROM truewealth_snapshots WHERE snapshot_id=?", (result["snapshot_id"],))


def test_confirm_rollback_removes_new_archive(monkeypatch, tmp_path):
    conn = make_conn()
    monkeypatch.setenv("JARVIS_FINANCE_RUNTIME_DIR", str(tmp_path))
    raw = b"%PDF-synthetic-rollback"
    monkeypatch.setattr(service, "parse_truewealth_tax_statement", lambda _raw: statement(raw))
    request = import_request(raw)
    preview = service.preview_truewealth_import(conn, request)
    conn.execute(
        "CREATE TRIGGER force_truewealth_failure BEFORE INSERT ON truewealth_import_batches "
        "BEGIN SELECT RAISE(ABORT, 'forced'); END"
    )
    with pytest.raises(ValueError, match="conflicted"):
        service.confirm_truewealth_import(
            conn,
            {**request, "preview_id": preview["preview_id"], "confirmation_id": preview["confirmation_id"], "confirm": True},
        )
    assert not list((tmp_path / "imports" / "truewealth" / "archive").glob("*.pdf"))
    assert conn.execute("SELECT COUNT(*) FROM truewealth_import_batches").fetchone()[0] == 0


def test_same_date_different_official_source_is_blocked(monkeypatch, tmp_path):
    conn = make_conn()
    monkeypatch.setenv("JARVIS_FINANCE_RUNTIME_DIR", str(tmp_path))
    monkeypatch.setattr(service, "parse_truewealth_tax_statement", lambda value: statement(value))
    confirm_import(conn, b"%PDF-1.7 source-one")
    second = import_request(b"%PDF-1.7 source-two")
    preview = service.preview_truewealth_import(conn, second)
    assert preview["conflict"] is True
    with pytest.raises(ValueError, match="different official TrueWealth source"):
        service.confirm_truewealth_import(
            conn,
            {**second, "preview_id": preview["preview_id"], "confirmation_id": preview["confirmation_id"], "confirm": True},
        )
    assert conn.execute("SELECT COUNT(*) FROM truewealth_import_batches").fetchone()[0] == 1


def test_manual_non_finite_values_are_rejected():
    conn = make_conn()
    for value in ("NaN", "Infinity", "-Infinity"):
        with pytest.raises(ValueError, match="positive finite"):
            service.preview_manual_truewealth_value(
                conn,
                {"total_value_chf": value, "valuation_at": "2026-07-28T08:00:00Z", "note": ""},
            )


def test_manual_value_is_not_hard_deleted_or_overwritten():
    conn = make_conn()
    payload = {"total_value_chf": "12500", "valuation_at": "2026-07-28T08:00:00Z", "note": "immutable value"}
    preview = service.preview_manual_truewealth_value(conn, payload)
    result = service.confirm_manual_truewealth_value(
        conn,
        {**payload, "preview_id": preview["preview_id"], "confirmation_id": preview["confirmation_id"], "confirm": True},
    )
    with pytest.raises(Exception, match="fields are immutable"):
        conn.execute("UPDATE account_value_snapshots SET total_value_chf='1' WHERE snapshot_id=?", (result["snapshot_id"],))
    with pytest.raises(Exception, match="cannot be deleted"):
        conn.execute("DELETE FROM account_value_snapshots WHERE snapshot_id=?", (result["snapshot_id"],))
