from __future__ import annotations

import re
from datetime import datetime
from typing import Any
from urllib.parse import urlparse

from jarvis_gateway.contracts import (
    ActionDescriptor,
    ActionMode,
    AttentionItem,
    DisplayPolicy,
    KpiItem,
    ModuleId,
    ModuleSnapshot,
    ModuleStatus,
    Sensitivity,
    Severity,
    SourceHealth,
    SourceType,
    utcnow,
)
from jarvis_gateway.redaction import assert_safe_module_snapshot

FORBIDDEN_FINANCE_PATTERNS = [
    r"amount_chf",
    r"cash_value_chf",
    r"total_value_chf",
    r"portfolio_value_chf",
    r"balance",
    r"saldo",
    r"revenue",
    r"expense",
    r"budget[_\s-]*amount",
    r"account[_\s-]*name",
    r"iban",
    r"transaction",
    r"csv",
    r"xlsx",
    r"pdf",
    r"db[_\s-]*path",
    r"runtime[_\s-]*path",
    r"local_original_path",
    r"drive_web_url",
    r"token",
    r"secret",
    r"password",
    r"api_key",
    r"/home/agent",
    r"finance\.sqlite",
]
_COMPILED = [re.compile(pattern, re.IGNORECASE) for pattern in FORBIDDEN_FINANCE_PATTERNS]


def _is_safe_local_url(url: str | None) -> bool:
    if not url:
        return False
    parsed = urlparse(url)
    return parsed.scheme == "http" and parsed.hostname in {"127.0.0.1", "localhost"}


def _bool_from(payload: dict[str, Any], *keys: str) -> bool | None:
    for key in keys:
        value = payload.get(key)
        if isinstance(value, bool):
            return value
        if isinstance(value, dict):
            nested = _bool_from(value, "ok", "connected", "reachable", "healthy")
            if nested is not None:
                return nested
    return None


def _safe_timestamp(payload: dict[str, Any]) -> datetime | None:
    for key in ("last_success_at", "last_successful_sync_at", "last_sync_at", "generated_at"):
        value = payload.get(key)
        if isinstance(value, str) and not any(pattern.search(value) for pattern in _COMPILED):
            try:
                return datetime.fromisoformat(value.replace("Z", "+00:00"))
            except ValueError:
                continue
    return None


def _count_value(value: Any) -> int | None:
    if isinstance(value, int) and 0 <= value <= 9999:
        return value
    if isinstance(value, list):
        return min(len(value), 9999)
    if isinstance(value, dict):
        for key in ("review_items", "review_count", "pending_reviews", "count"):
            found = _count_value(value.get(key))
            if found is not None:
                return found
    return None


def _sum_key(value: Any, key_name: str) -> int:
    if isinstance(value, dict):
        total = 0
        for key, nested in value.items():
            if key == key_name and isinstance(nested, int) and 0 <= nested <= 9999:
                total += nested
            else:
                total += _sum_key(nested, key_name)
        return total
    if isinstance(value, list):
        return sum(_sum_key(item, key_name) for item in value)
    return 0


def _provider_connected(provider_payload: Any) -> bool | None:
    if isinstance(provider_payload, list):
        statuses = [str(item.get("status", "")).lower() for item in provider_payload if isinstance(item, dict)]
        if not statuses:
            return None
        if any(status in {"ok", "connected", "ready", "healthy"} for status in statuses):
            return True
        if any(status in {"degraded", "offline", "error"} for status in statuses):
            return False
        return None
    if isinstance(provider_payload, dict):
        return _bool_from(provider_payload, "connected", "ok", "reachable")
    return None


def _status_from_sources(provider_ok: bool | None, runtime_ok: bool | None, audit_status: str) -> ModuleStatus:
    if runtime_ok is False:
        return ModuleStatus.DEGRADED
    if provider_ok is False or audit_status in {"warning", "error"}:
        return ModuleStatus.ATTENTION
    return ModuleStatus.OK


def _attention(now: datetime, title: str, message: str, severity: Severity, dedupe: str) -> AttentionItem:
    return AttentionItem(
        id=f"att_finance_{dedupe}",
        module_id=ModuleId.FINANCE,
        title=title,
        message=message,
        severity=severity,
        sensitivity=Sensitivity.SENSITIVE,
        created_at=now,
        action=ActionDescriptor(
            label="Finance prüfen",
            href="/finance",
            mode=ActionMode.LINK_ONLY,
            sensitivity=Sensitivity.SENSITIVE,
            blocked_reason=None,
            preview_required=False,
            confirm_required=False,
            audit_required=False,
        ),
        dedupe_key=f"finance.{dedupe}",
    )


def sanitize_finance_payload(raw: dict[str, Any], *, legacy_dashboard_url: str | None = None, last_attempt_at: datetime | None = None) -> ModuleSnapshot:
    if legacy_dashboard_url and not _is_safe_local_url(legacy_dashboard_url):
        raise ValueError("unsafe legacy dashboard URL")
    now = last_attempt_at or utcnow()
    provider = raw.get("provider") if isinstance(raw.get("provider"), dict) else raw.get("provider/status", {})
    runtime = raw.get("runtime") if isinstance(raw.get("runtime"), dict) else raw.get("runtime/status", {})
    health = raw.get("health") if isinstance(raw.get("health"), dict) else raw.get("/health", {})
    budget = raw.get("budget") if isinstance(raw.get("budget"), dict) else raw.get("budget/import-status-audit", {})
    provider_payload = provider
    provider = provider if isinstance(provider, dict) else {}
    runtime = runtime if isinstance(runtime, dict) else {}
    health = health if isinstance(health, dict) else {}
    budget = budget if isinstance(budget, dict) else {}
    provider_ok = _provider_connected(provider_payload)
    runtime_ok = _bool_from(runtime, "reachable", "ok", "healthy")
    health_ok = _bool_from(health, "ok", "healthy", "reachable")
    reachable = runtime_ok is not False and health_ok is not False
    audit_status_raw = budget.get("status") or budget.get("import_audit") or budget.get("audit_status") or "ok"
    audit_status = str(audit_status_raw).lower() if str(audit_status_raw).lower() in {"ok", "warning", "error"} else "ok"
    review_count = _count_value(budget) or _sum_key(budget, "review_needed") or _count_value(raw.get("reviews")) or 0
    last_success = _safe_timestamp(health) or _safe_timestamp(runtime) or _safe_timestamp(raw) or (now if reachable else None)
    status = _status_from_sources(provider_ok, runtime_ok, audit_status)
    kpis = [
        KpiItem(key="review_items", label="Review Items", value=review_count, unit="items", severity=Severity.WARNING if review_count else Severity.SUCCESS, sensitivity=Sensitivity.SENSITIVE, display_policy=DisplayPolicy.SUMMARY, help_text="Count only; no amounts."),
        KpiItem(key="provider_connected", label="Provider verbunden", value="Ja" if provider_ok else "Nein" if provider_ok is False else "Unbekannt", unit=None, severity=Severity.SUCCESS if provider_ok else Severity.WARNING, sensitivity=Sensitivity.INTERNAL, display_policy=DisplayPolicy.SUMMARY, help_text="Status only."),
        KpiItem(key="runtime_reachable", label="Runtime erreichbar", value="Ja" if reachable else "Nein", unit=None, severity=Severity.SUCCESS if reachable else Severity.ERROR, sensitivity=Sensitivity.INTERNAL, display_policy=DisplayPolicy.SUMMARY, help_text="Reachability only."),
    ]
    attention: list[AttentionItem] = []
    if review_count:
        attention.append(_attention(now, "Review nötig", "FinanceManager meldet Review-Bedarf ohne Beträge.", Severity.WARNING, "review"))
    if provider_ok is False:
        attention.append(_attention(now, "Provider degradiert", "Finance Provider ist nicht verbunden.", Severity.WARNING, "provider"))
    if audit_status in {"warning", "error"}:
        attention.append(_attention(now, "Import Audit prüfen", "Import Audit meldet einen sicheren Warnstatus.", Severity.WARNING, "audit"))
    if not reachable:
        attention.append(_attention(now, "Runtime offline", "FinanceManager Runtime ist nicht erreichbar.", Severity.ERROR, "runtime"))
    primary = ActionDescriptor(label="Open Finance", href="/finance", mode=ActionMode.LINK_ONLY, sensitivity=Sensitivity.SENSITIVE, blocked_reason=None, preview_required=False, confirm_required=False, audit_required=False)
    links = [primary]
    if legacy_dashboard_url:
        links.append(ActionDescriptor(label="Legacy Finance Dashboard", href=legacy_dashboard_url, mode=ActionMode.LINK_ONLY, sensitivity=Sensitivity.SENSITIVE, blocked_reason=None, preview_required=False, confirm_required=False, audit_required=False))
    snapshot = ModuleSnapshot(
        module_id=ModuleId.FINANCE,
        title="FinanceManager",
        status=status,
        sensitivity=Sensitivity.SENSITIVE,
        display_policy=DisplayPolicy.SUMMARY,
        last_success_at=last_success,
        last_attempt_at=now,
        stale_after_seconds=300,
        degraded_reason=None if status in {ModuleStatus.OK, ModuleStatus.ATTENTION} else "finance_source_degraded",
        source_health=SourceHealth(reachable=reachable, latency_ms=None, source_type=SourceType.HTTP_API, version="finance-readonly-v1", contract_version="jarvis.module_snapshot.v1", last_success_at=last_success, last_error_redacted=None, stale=not reachable),
        kpis=kpis,
        attention_items=attention[:5],
        links=links,
        primary_action=primary,
        contract_version="jarvis.module_snapshot.v1",
    )
    payload = snapshot.model_dump(mode="json")
    assert_no_sensitive_finance_output(payload)
    assert_safe_module_snapshot(payload)
    return snapshot


def assert_no_sensitive_finance_output(payload: Any) -> None:
    text = str(payload)
    for pattern in _COMPILED:
        if pattern.search(text):
            raise ValueError("finance snapshot contains sensitive value")
