from __future__ import annotations

from decimal import Decimal
from pathlib import Path

from jarvis_finance.config.paths import RuntimePaths
from jarvis_finance.config.settings import Settings
from jarvis_finance.crypto.assets import create_crypto_asset
from jarvis_finance.crypto.holdings import create_initial_holding_snapshot
from jarvis_finance.crypto.wallets import create_wallet
from jarvis_finance.dashboard.demo_data import load_synthetic_demo_data
from jarvis_finance.reports.crypto_inventory import (
    build_crypto_report_context,
    export_crypto_inventory_report,
)
from jarvis_finance.storage.database import connect_memory
from jarvis_finance.storage.migrations import apply_migrations


def setup_demo_conn():
    conn = connect_memory()
    apply_migrations(conn)
    load_synthetic_demo_data(conn)
    return conn


def test_crypto_report_context_from_synthetic_db_aggregates_coins_and_wallets() -> None:
    conn = setup_demo_conn()

    context = build_crypto_report_context(conn)

    assert context["title"] == "JARVIS Finance System – Crypto-Bestandsübersicht"
    assert context["base_currency"] == "CHF"
    assert context["summary"]["coin_count"] >= 2
    assert context["summary"]["wallet_count"] >= 2
    btc = next(row for row in context["coins"] if row["symbol"] == "DBTC")
    assert btc["total_quantity"] == "0.0999"
    assert btc["price_chf"] == "40000.00"
    assert btc["value_chf"] == "3996.0000"
    assert context["summary"]["total_value_chf"] == "7746.0000"
    kraken = next(row for row in context["wallets"] if row["wallet_name"] == "Kraken Demo")
    assert kraken["coin_count"] == 2


def test_crypto_report_context_preserves_decimal_text_and_ignores_legacy_snapshot_values() -> None:
    conn = setup_demo_conn()
    conn.execute(
        "UPDATE crypto_holdings SET legacy_snapshot_value_chf=?, legacy_snapshot_currency=? WHERE quantity=?",
        ("999999999.99", "CHF", "0.10"),
    )
    conn.commit()

    context = build_crypto_report_context(conn)

    btc = next(row for row in context["coins"] if row["symbol"] == "DBTC")
    assert btc["total_quantity"] == "0.0999"
    assert btc["value_chf"] == "3996.0000"
    assert context["summary"]["total_value_chf"] != "999999999.99"
    assert any(w["code"] == "legacy_snapshot_value_ignored" for w in context["data_quality_warnings"])


def test_crypto_report_context_warns_for_missing_price_and_missing_coingecko_id() -> None:
    conn = setup_demo_conn()
    wallet_id = conn.execute("SELECT wallet_id FROM crypto_wallets LIMIT 1").fetchone()["wallet_id"]
    asset_id = create_crypto_asset(conn, coin_name="Demo Missing", symbol="DMX", coingecko_id=None)
    create_initial_holding_snapshot(
        conn,
        asset_id=asset_id,
        wallet_id=wallet_id,
        quantity=Decimal("12.34567890123456789"),
        verification_status="verified",
        last_verified_at="2026-01-31T12:00:00Z",
        note="synthetic missing price",
    )

    context = build_crypto_report_context(conn)

    dmx = next(row for row in context["coins"] if row["symbol"] == "DMX")
    assert dmx["total_quantity"] == "12.34567890123456789"
    codes = {warning["code"] for warning in context["data_quality_warnings"]}
    assert "missing_coingecko_id" in codes
    assert "missing_price" in codes


def test_crypto_report_context_warns_for_unverified_wallet_and_negative_holding() -> None:
    conn = setup_demo_conn()
    asset_id = conn.execute("SELECT asset_id FROM crypto_assets WHERE symbol='DETH'").fetchone()["asset_id"]
    wallet_id = create_wallet(
        conn,
        wallet_name="Unverified Demo Wallet",
        wallet_type="Software Wallet",
        platform_provider="Synthetic",
        last_verified_at=None,
    )
    conn.execute(
        """
        INSERT INTO crypto_holdings(
            crypto_holding_id, asset_id, wallet_id, quantity, acquisition_source,
            verification_status, notes, created_at
        ) VALUES ('negative-demo', ?, ?, '-0.25', 'initial_snapshot', 'unverified', 'synthetic negative', '2026-01-31T00:00:00Z')
        """,
        (asset_id, wallet_id),
    )
    conn.commit()

    context = build_crypto_report_context(conn)

    codes = {warning["code"] for warning in context["data_quality_warnings"]}
    assert "unverified_wallet" in codes
    assert "negative_holding" in codes


def test_crypto_report_export_writes_runtime_report_metadata_and_audit(tmp_path: Path) -> None:
    conn = setup_demo_conn()
    runtime = RuntimePaths.from_base(tmp_path / "runtime")
    settings = Settings(runtime_paths=runtime)

    result = export_crypto_inventory_report(conn, settings=settings, preferred_format="pdf", renderer=lambda html, path: False)

    output_path = Path(result["file_path"])
    assert output_path.exists()
    assert output_path.suffix == ".html"
    assert runtime.reports_dir in output_path.parents
    assert not Path(result["file_path"]).resolve().is_relative_to(Path.cwd().resolve())
    report = conn.execute("SELECT * FROM reports WHERE report_type='crypto_inventory'").fetchone()
    assert report is not None
    assert report["format"] == "html"
    assert report["file_path"] == str(output_path)
    audit = conn.execute("SELECT * FROM audit_log WHERE action='report_generated'").fetchone()
    assert audit is not None


def test_crypto_report_export_markdown_fallback_when_pdf_renderer_unavailable(tmp_path: Path) -> None:
    conn = setup_demo_conn()
    runtime = RuntimePaths.from_base(tmp_path / "runtime")
    settings = Settings(runtime_paths=runtime)

    result = export_crypto_inventory_report(conn, settings=settings, preferred_format="pdf", renderer=None)

    output_path = Path(result["file_path"])
    assert output_path.exists()
    assert output_path.suffix in {".html", ".md"}
    assert result["format"] in {"html", "markdown"}
    assert result["warnings"]
    assert all(not p.is_relative_to(Path.cwd().resolve()) for p in runtime.reports_dir.glob("*"))
