from __future__ import annotations

import html
import json
import uuid
from datetime import datetime, timezone
from decimal import Decimal, ROUND_HALF_UP
from pathlib import Path
from sqlite3 import Connection
from typing import Any, Callable

from jarvis_finance.audit.log import record_audit_event
from jarvis_finance.config.settings import Settings, ensure_runtime_dirs, validate_settings
from jarvis_finance.crypto.holdings import calculate_crypto_holdings
from jarvis_finance.imports.common import utc_now
from jarvis_finance.market.providers import is_price_stale

PDFRenderer = Callable[[str, Path], bool]


def _d(value: object, default: str = "0") -> Decimal:
    if value is None or str(value).strip() == "":
        return Decimal(default)
    return Decimal(str(value))


def _dec_text(value: Decimal | None, places: str | None = None) -> str | None:
    if value is None:
        return None
    if places is not None:
        value = value.quantize(Decimal(places), rounding=ROUND_HALF_UP)
    return format(value, "f")


def _latest_crypto_price(conn: Connection, asset_id: str, currency: str) -> Any | None:
    return conn.execute(
        """
        SELECT * FROM crypto_prices
        WHERE asset_id=? AND price_currency=?
        ORDER BY COALESCE(provider_timestamp, fetched_at, '') DESC, fetched_at DESC
        LIMIT 1
        """,
        (asset_id, currency),
    ).fetchone()


def _parse_dt(value: str | None) -> datetime | None:
    if not value:
        return None
    try:
        return datetime.fromisoformat(value.replace("Z", "+00:00"))
    except ValueError:
        return None


def _is_stale(value: str | None, *, max_age_seconds: int = 259_200) -> bool:
    return is_price_stale(value, max_age_seconds=max_age_seconds)


def _add_warning(warnings: list[dict[str, str]], *, code: str, message: str, entity_type: str, entity_id: str | None = None) -> None:
    item = {"code": code, "message": message, "entity_type": entity_type}
    if entity_id:
        item["entity_id"] = entity_id
    if item not in warnings:
        warnings.append(item)


def build_crypto_report_context(conn: Connection, *, base_currency: str = "CHF", price_max_age_seconds: int = 259_200) -> dict[str, Any]:
    """Build a renderer-neutral crypto inventory report context from local SQLite data only."""
    holdings = calculate_crypto_holdings(conn)
    warnings: list[dict[str, str]] = []
    generated_at = utc_now()

    wallet_rows = {row["wallet_id"]: row for row in conn.execute("SELECT * FROM crypto_wallets").fetchall()}
    asset_rows = {row["asset_id"]: row for row in conn.execute("SELECT * FROM crypto_assets").fetchall()}

    coin_rows: list[dict[str, Any]] = []
    total_value = Decimal("0")
    valued_asset_ids: set[str] = set()
    latest_price_timestamp: str | None = None
    latest_wallet_verification: str | None = None

    for asset_id, total_holding in sorted(holdings.total_by_asset.items()):
        asset = asset_rows.get(asset_id)
        if asset is None:
            continue
        quantity = total_holding.quantity
        if quantity < 0:
            _add_warning(warnings, code="negative_holding", message="Negativer Crypto-Bestand erkannt.", entity_type="crypto_asset", entity_id=asset_id)
        if not asset["coingecko_id"]:
            _add_warning(warnings, code="missing_coingecko_id", message="CoinGecko-ID fehlt; Bewertung ist eingeschränkt.", entity_type="crypto_asset", entity_id=asset_id)
        price = _latest_crypto_price(conn, asset_id, base_currency)
        price_dec: Decimal | None = None
        price_timestamp: str | None = None
        provider: str | None = None
        if price is None or price["price"] is None:
            _add_warning(warnings, code="missing_price", message="Lokaler Crypto-Preis fehlt.", entity_type="crypto_asset", entity_id=asset_id)
        else:
            price_dec = _d(price["price"])
            price_timestamp = price["provider_timestamp"] or price["fetched_at"]
            provider = price["provider"]
            if _is_stale(price_timestamp, max_age_seconds=price_max_age_seconds):
                _add_warning(warnings, code="stale_price", message="Lokaler Crypto-Preis ist veraltet.", entity_type="crypto_asset", entity_id=asset_id)
            latest_price_timestamp = max(filter(None, [latest_price_timestamp, price_timestamp])) if latest_price_timestamp else price_timestamp
        value = quantity * price_dec if price_dec is not None else None
        if value is not None:
            total_value += value
            valued_asset_ids.add(asset_id)

        has_legacy = any(
            h.asset_id == asset_id and (h.legacy_snapshot_value_chf is not None or h.legacy_snapshot_value_original is not None)
            for h in holdings.wallet_holdings.values()
        )
        if has_legacy:
            _add_warning(warnings, code="legacy_snapshot_value_ignored", message="Legacy Snapshot Value vorhanden, aber für aktuelle Bewertung ignoriert.", entity_type="crypto_asset", entity_id=asset_id)

        coin_rows.append(
            {
                "asset_id": asset_id,
                "coin_name": asset["coin_name"],
                "symbol": asset["symbol"],
                "coingecko_id": asset["coingecko_id"],
                "total_quantity": _dec_text(quantity),
                "price_chf": _dec_text(price_dec),
                "value_chf": _dec_text(value, "0.0001"),
                "portfolio_share_pct": None,
                "price_source": provider,
                "price_timestamp": price_timestamp,
                "warning": ", ".join(w["code"] for w in warnings if w.get("entity_id") == asset_id) or None,
            }
        )

    for row in coin_rows:
        if row["asset_id"] in valued_asset_ids and total_value != 0:
            share = (_d(row["value_chf"]) / total_value * Decimal("100"))
            row["portfolio_share_pct"] = _dec_text(share, "0.01")

    wallet_agg: dict[str, dict[str, Any]] = {}
    detail_rows: list[dict[str, Any]] = []
    for (wallet_id, asset_id), holding in sorted(holdings.wallet_holdings.items()):
        wallet = wallet_rows.get(wallet_id)
        asset = asset_rows.get(asset_id)
        if wallet is None or asset is None:
            continue
        if wallet["last_verified_at"]:
            latest_wallet_verification = max(filter(None, [latest_wallet_verification, wallet["last_verified_at"]])) if latest_wallet_verification else wallet["last_verified_at"]
        if wallet["last_verified_at"] is None or holding.verification_status != "verified":
            _add_warning(warnings, code="unverified_wallet", message="Wallet ist nicht verifiziert oder Holding-Status ist nicht verified.", entity_type="crypto_wallet", entity_id=wallet_id)
        if holding.quantity < 0:
            _add_warning(warnings, code="negative_holding", message="Negativer Wallet-Bestand erkannt.", entity_type="crypto_wallet", entity_id=wallet_id)
        price = _latest_crypto_price(conn, asset_id, base_currency)
        price_dec = _d(price["price"]) if price and price["price"] is not None else None
        value = holding.quantity * price_dec if price_dec is not None else None
        agg = wallet_agg.setdefault(
            wallet_id,
            {
                "wallet_id": wallet_id,
                "wallet_name": wallet["wallet_name"],
                "wallet_type": wallet["wallet_type"],
                "provider": wallet["platform_provider"],
                "coin_ids": set(),
                "total_value_chf_dec": Decimal("0"),
                "last_verified_at": wallet["last_verified_at"],
                "is_active": bool(wallet["is_active"]),
            },
        )
        agg["coin_ids"].add(asset_id)
        if value is not None:
            agg["total_value_chf_dec"] += value
        detail_rows.append(
            {
                "wallet_id": wallet_id,
                "wallet_name": wallet["wallet_name"],
                "asset_id": asset_id,
                "coin": asset["coin_name"],
                "symbol": asset["symbol"],
                "quantity": _dec_text(holding.quantity),
                "price_chf": _dec_text(price_dec),
                "value_chf": _dec_text(value, "0.0001"),
                "last_verified_at": wallet["last_verified_at"] or holding.last_verified_at,
                "verification_status": holding.verification_status,
                "notes": None,
            }
        )

    wallet_summary = []
    for row in wallet_agg.values():
        wallet_summary.append(
            {
                "wallet_id": row["wallet_id"],
                "wallet_name": row["wallet_name"],
                "wallet_type": row["wallet_type"],
                "provider": row["provider"],
                "coin_count": len(row["coin_ids"]),
                "total_value_chf": _dec_text(row["total_value_chf_dec"]),
                "last_verified_at": row["last_verified_at"],
                "is_active": row["is_active"],
            }
        )

    status = "ok"
    if any(w["code"] in {"negative_holding", "missing_price", "missing_coingecko_id"} for w in warnings):
        status = "warning"
    if any(w["code"] == "negative_holding" for w in warnings):
        status = "critical"

    return {
        "title": "JARVIS Finance System – Crypto-Bestandsübersicht",
        "generated_at": generated_at,
        "base_currency": base_currency,
        "price_data_as_of": latest_price_timestamp,
        "wallet_verification_as_of": latest_wallet_verification,
        "disclaimer": "Bestandsübersicht, keine vollständige Steuererklärung.",
        "summary": {
            "total_value_chf": _dec_text(total_value, "0.0001"),
            "coin_count": len(coin_rows),
            "wallet_count": len(wallet_summary),
            "data_quality_status": status,
        },
        "coins": coin_rows,
        "wallets": sorted(wallet_summary, key=lambda r: r["wallet_name"]),
        "coin_wallet_details": detail_rows,
        "data_quality_warnings": warnings,
        "metadata": {
            "report_type": "crypto_inventory",
            "source": "local_sqlite",
            "live_api_calls": False,
            "currencies_prepared": ["CHF", "USD", "EUR"],
        },
    }


def render_crypto_report_markdown(context: dict[str, Any]) -> str:
    lines = [
        f"# {context['title']}",
        "",
        f"Erstellt: {context['generated_at']}",
        f"Basiswährung: {context['base_currency']}",
        f"Datenstand Preise: {context.get('price_data_as_of') or 'nicht verfügbar'}",
        f"Datenstand Wallet-Verifikation: {context.get('wallet_verification_as_of') or 'nicht verfügbar'}",
        "",
        f"Disclaimer: {context['disclaimer']}",
        "",
        "## Executive Summary",
        f"- Gesamtwert Crypto CHF: {context['summary']['total_value_chf']} CHF",
        f"- Anzahl Coins: {context['summary']['coin_count']}",
        f"- Anzahl Wallets/Plattformen: {context['summary']['wallet_count']}",
        f"- Datenqualitätsstatus: {context['summary']['data_quality_status']}",
        "",
        "## Coin-Übersicht",
    ]
    for row in context["coins"]:
        lines.append(f"- {row['coin_name']} ({row['symbol']}): {row['total_quantity']} @ {row['price_chf'] or 'n/a'} CHF = {row['value_chf'] or 'n/a'} CHF; Anteil {row['portfolio_share_pct'] or 'n/a'}%; Quelle {row['price_source'] or 'n/a'}; Preiszeit {row['price_timestamp'] or 'n/a'}; Warnung {row['warning'] or '-'}")
    lines.extend(["", "## Wallet-Übersicht"])
    for row in context["wallets"]:
        lines.append(f"- {row['wallet_name']} ({row['wallet_type']}, {row['provider'] or 'n/a'}): {row['coin_count']} Coins, {row['total_value_chf']} CHF, letzte Verifikation {row['last_verified_at'] or 'n/a'}")
    lines.extend(["", "## Detail pro Coin nach Wallet"])
    for row in context["coin_wallet_details"]:
        lines.append(f"- {row['wallet_name']} / {row['symbol']}: Menge {row['quantity']}, Kurs {row['price_chf'] or 'n/a'}, Wert {row['value_chf'] or 'n/a'} CHF, Verifikation {row['last_verified_at'] or 'n/a'}, Notizen {row['notes'] or '-'}")
    lines.extend(["", "## Datenqualitätswarnungen"])
    if context["data_quality_warnings"]:
        for warning in context["data_quality_warnings"]:
            lines.append(f"- {warning['code']}: {warning['message']}")
    else:
        lines.append("- Keine Warnungen.")
    lines.append("")
    return "\n".join(lines)


def render_crypto_report_html(context: dict[str, Any]) -> str:
    markdown = render_crypto_report_markdown(context)
    body = "\n".join(f"<p>{html.escape(line)}</p>" if line else "" for line in markdown.splitlines())
    return "<!doctype html><html><head><meta charset='utf-8'><title>Crypto-Bestandsübersicht</title></head><body>" + body + "</body></html>"


def _default_pdf_renderer(html_text: str, output_path: Path) -> bool:
    try:
        from weasyprint import HTML  # type: ignore
    except Exception:
        return False
    HTML(string=html_text).write_pdf(str(output_path))
    return True


def export_crypto_inventory_report(
    conn: Connection,
    *,
    settings: Settings,
    preferred_format: str = "pdf",
    renderer: PDFRenderer | None = _default_pdf_renderer,
) -> dict[str, Any]:
    validate_settings(settings)
    ensure_runtime_dirs(settings)
    reports_dir = settings.runtime_paths.reports_dir.resolve()
    report_id = str(uuid.uuid4())
    context = build_crypto_report_context(conn, base_currency=settings.base_currency)
    stem = f"crypto_inventory_{context['generated_at'].replace(':', '').replace('+', '_')}_{report_id[:8]}"
    warnings: list[str] = []
    html_text = render_crypto_report_html(context)
    markdown_text = render_crypto_report_markdown(context)

    fmt = preferred_format.lower()
    if fmt == "pdf" and renderer is not None:
        pdf_path = reports_dir / f"{stem}.pdf"
        if renderer(html_text, pdf_path):
            output_path = pdf_path
            final_format = "pdf"
        else:
            warnings.append("PDF renderer unavailable; wrote HTML fallback.")
            output_path = reports_dir / f"{stem}.html"
            output_path.write_text(html_text, encoding="utf-8")
            final_format = "html"
    elif fmt == "markdown":
        output_path = reports_dir / f"{stem}.md"
        output_path.write_text(markdown_text, encoding="utf-8")
        final_format = "markdown"
    else:
        warnings.append("PDF renderer unavailable; wrote HTML fallback." if preferred_format.lower() == "pdf" else "Wrote HTML report.")
        output_path = reports_dir / f"{stem}.html"
        output_path.write_text(html_text, encoding="utf-8")
        final_format = "html"

    now = utc_now()
    summary_json = json.dumps(context["summary"], sort_keys=True)
    conn.execute(
        """
        INSERT INTO reports(
            report_id, report_type, title, generated_at, file_path, format,
            data_quality_status, summary_json, created_at
        ) VALUES (?, 'crypto_inventory', ?, ?, ?, ?, ?, ?, ?)
        """,
        (
            report_id,
            context["title"],
            now,
            str(output_path),
            final_format,
            context["summary"]["data_quality_status"],
            summary_json,
            now,
        ),
    )
    record_audit_event(
        conn,
        source="reports",
        action="report_generated",
        entity_type="report",
        entity_id=report_id,
        new_values={"report_type": "crypto_inventory", "file_path": str(output_path), "format": final_format},
        confirmed=True,
        created_by="system",
    )
    conn.commit()
    return {"report_id": report_id, "file_path": str(output_path), "format": final_format, "warnings": warnings, "context": context}
