from __future__ import annotations

from dataclasses import asdict, dataclass
from datetime import datetime, timezone
import json
import os
from pathlib import Path
from typing import Any, Iterable, Literal

ApprovalDecision = Literal["approve", "reject", "paper_only"]
VALID_DECISIONS: set[str] = {"approve", "reject", "paper_only"}
DEFAULT_APPROVAL_LOG = Path("/home/agent/jarvis_runtime/finance-system/crypto_trader/trade_approvals.jsonl")
APPROVAL_LOG_ENV = "CTB_FINANCE_APPROVAL_LOG"
APPROVAL_MAX_AGE_SECONDS_ENV = "CTB_FINANCE_APPROVAL_MAX_AGE_SECONDS"


@dataclass(frozen=True)
class FinanceTradeApproval:
    approval_id: str
    intent_id: str
    decision: ApprovalDecision
    created_at: str
    note: str = ""
    strategy_id: str | None = None
    coin: str | None = None
    source_engine: str | None = None
    portfolio_id: str | None = None
    side: str | None = None
    signal: str | None = None
    intent_created_at: str | None = None
    execution_allowed: bool = False
    boundary: str = ""

    @property
    def created_at_dt(self) -> datetime | None:
        try:
            return datetime.fromisoformat(self.created_at.replace("Z", "+00:00"))
        except ValueError:
            return None


@dataclass(frozen=True)
class ApprovalGateDecision:
    allowed: bool
    status: str
    reasons: tuple[str, ...]
    intent_id: str | None = None
    approval_id: str | None = None
    decision: str | None = None
    mainnet_signed_action: bool = False

    def to_dict(self) -> dict[str, Any]:
        return asdict(self)


def approval_log_path() -> Path:
    return Path(os.getenv(APPROVAL_LOG_ENV, str(DEFAULT_APPROVAL_LOG))).expanduser()


def _parse_approval(row: dict[str, Any]) -> FinanceTradeApproval | None:
    intent_id = row.get("intent_id")
    decision = str(row.get("decision") or "").strip().lower()
    created_at = row.get("created_at")
    approval_id = row.get("approval_id")
    if not isinstance(intent_id, str) or not intent_id:
        return None
    if decision not in VALID_DECISIONS:
        return None
    if not isinstance(created_at, str) or not created_at:
        return None
    if not isinstance(approval_id, str) or not approval_id:
        return None
    return FinanceTradeApproval(
        approval_id=approval_id,
        intent_id=intent_id,
        decision=decision,  # type: ignore[arg-type]
        created_at=created_at,
        note=str(row.get("note") or ""),
        strategy_id=str(row.get("strategy_id")) if row.get("strategy_id") else None,
        coin=str(row.get("coin")).upper() if row.get("coin") else None,
        source_engine=str(row.get("source_engine")) if row.get("source_engine") else None,
        portfolio_id=str(row.get("portfolio_id")) if row.get("portfolio_id") else None,
        side=str(row.get("side")) if row.get("side") else None,
        signal=str(row.get("signal")) if row.get("signal") else None,
        intent_created_at=str(row.get("intent_created_at")) if row.get("intent_created_at") else None,
        execution_allowed=bool(row.get("execution_allowed", False)),
        boundary=str(row.get("boundary") or ""),
    )


def load_finance_approvals(path: Path | None = None, *, limit: int = 1000) -> list[FinanceTradeApproval]:
    source = path or approval_log_path()
    if not source.exists():
        return []
    try:
        lines = source.read_text(encoding="utf-8", errors="replace").splitlines()[-limit:]
    except OSError:
        return []
    approvals: list[FinanceTradeApproval] = []
    for line in lines:
        if not line.strip():
            continue
        try:
            payload = json.loads(line)
        except json.JSONDecodeError:
            continue
        if not isinstance(payload, dict):
            continue
        approval = _parse_approval(payload)
        if approval is not None:
            approvals.append(approval)
    return approvals


def latest_approval_by_intent(approvals: Iterable[FinanceTradeApproval]) -> dict[str, FinanceTradeApproval]:
    latest: dict[str, FinanceTradeApproval] = {}
    for approval in approvals:
        current = latest.get(approval.intent_id)
        current_dt = current.created_at_dt if current else None
        approval_dt = approval.created_at_dt
        if current is None or (approval_dt is not None and (current_dt is None or approval_dt >= current_dt)):
            latest[approval.intent_id] = approval
    return latest


def latest_approval_by_strategy_coin(approvals: Iterable[FinanceTradeApproval]) -> dict[tuple[str, str], FinanceTradeApproval]:
    latest: dict[tuple[str, str], FinanceTradeApproval] = {}
    for approval in approvals:
        if not approval.strategy_id or not approval.coin:
            continue
        key = (approval.strategy_id, approval.coin.upper())
        current = latest.get(key)
        current_dt = current.created_at_dt if current else None
        approval_dt = approval.created_at_dt
        if current is None or (approval_dt is not None and (current_dt is None or approval_dt >= current_dt)):
            latest[key] = approval
    return latest


def _approval_to_gate(latest: FinanceTradeApproval, *, intent_id: str | None) -> ApprovalGateDecision:
    resolved_intent_id = intent_id or latest.intent_id
    if latest.decision == "reject":
        return ApprovalGateDecision(False, "blocked", ("finance_approval_rejected",), intent_id=resolved_intent_id, approval_id=latest.approval_id, decision=latest.decision)
    if latest.decision == "paper_only":
        return ApprovalGateDecision(False, "blocked", ("finance_approval_paper_only",), intent_id=resolved_intent_id, approval_id=latest.approval_id, decision=latest.decision)
    return ApprovalGateDecision(True, "approved_pending_crypto_trader_gates", ("crypto_trader_must_recheck_all_gates",), intent_id=resolved_intent_id, approval_id=latest.approval_id, decision=latest.decision)


def evaluate_finance_approval(
    intent_id: str | None,
    *,
    approvals: Iterable[FinanceTradeApproval] | None = None,
    max_age_seconds: int | None = None,
) -> ApprovalGateDecision:
    if not intent_id:
        return ApprovalGateDecision(False, "blocked", ("finance_approval_intent_id_missing",))
    rows = list(approvals) if approvals is not None else load_finance_approvals()
    latest = latest_approval_by_intent(rows).get(intent_id)
    if latest is None:
        return ApprovalGateDecision(False, "blocked", ("finance_approval_missing",), intent_id=intent_id)
    age_limit = max_age_seconds
    if age_limit is None:
        raw = os.getenv(APPROVAL_MAX_AGE_SECONDS_ENV)
        age_limit = int(raw) if raw and raw.isdigit() else None
    if age_limit is not None:
        created = latest.created_at_dt
        if created is None:
            return ApprovalGateDecision(False, "blocked", ("finance_approval_timestamp_invalid",), intent_id=intent_id, approval_id=latest.approval_id, decision=latest.decision)
        age = (datetime.now(timezone.utc) - created.astimezone(timezone.utc)).total_seconds()
        if age > age_limit:
            return ApprovalGateDecision(False, "blocked", ("finance_approval_expired",), intent_id=intent_id, approval_id=latest.approval_id, decision=latest.decision)
    return _approval_to_gate(latest, intent_id=intent_id)


def evaluate_finance_approval_for_strategy_coin(
    strategy_id: str | None,
    coin: str | None,
    *,
    approvals: Iterable[FinanceTradeApproval] | None = None,
) -> ApprovalGateDecision:
    if not strategy_id or not coin:
        return ApprovalGateDecision(False, "blocked", ("finance_approval_strategy_coin_missing",))
    rows = list(approvals) if approvals is not None else load_finance_approvals()
    latest = latest_approval_by_strategy_coin(rows).get((strategy_id, coin.upper()))
    if latest is None:
        return ApprovalGateDecision(False, "blocked", ("finance_approval_strategy_coin_missing",), intent_id=None)
    return _approval_to_gate(latest, intent_id=latest.intent_id)


def build_finance_approval_status(path: Path | None = None) -> dict[str, Any]:
    source = path or approval_log_path()
    approvals = load_finance_approvals(source)
    latest = latest_approval_by_intent(approvals)
    latest_by_strategy_coin = latest_approval_by_strategy_coin(approvals)
    decisions = {"approve": 0, "reject": 0, "paper_only": 0}
    for row in latest.values():
        decisions[row.decision] += 1
    return {
        "status": "ok" if source.exists() else "missing_approval_log",
        "source": str(source),
        "approvals_loaded": len(approvals),
        "unique_intents": len(latest),
        "unique_strategy_coin_pairs": len(latest_by_strategy_coin),
        "latest_decisions": decisions,
        "mainnet_signed_action": False,
        "execution_side_effects": False,
    }
