from __future__ import annotations

from dataclasses import dataclass
from typing import Any, Dict, List, Optional, Tuple
from datetime import datetime, timezone


def _fmt_usd(x: float, decimals: int = 2) -> str:
    return f"${x:,.{decimals}f}"


def _safe_float(v: Any, default: float = 0.0) -> float:
    try:
        return float(v)
    except Exception:
        return default


def _risk_rank(risk: str) -> int:
    r = (risk or "").strip().lower()
    return {"high": 3, "med": 2, "medium": 2, "low": 1}.get(r, 0)


def _summarize_risks(recs: List[Dict[str, Any]]) -> Dict[str, int]:
    counts = {"low": 0, "med": 0, "high": 0, "unknown": 0}
    for r in recs:
        risk = (r.get("risk") or "").strip().lower()
        if risk in ("low", "med", "high"):
            counts[risk] += 1
        elif risk == "medium":
            counts["med"] += 1
        else:
            counts["unknown"] += 1
    return counts


def _compute_payback_display(run_cost_usd: float, monthly_savings_usd: float) -> Tuple[Optional[float], str]:
    """
    Returns (payback_days, human_display_string).
    """
    if monthly_savings_usd <= 0:
        return None, "N/A"

    # dollars per day
    daily = monthly_savings_usd / 30.0
    if daily <= 0:
        return None, "N/A"

    days = run_cost_usd / daily if run_cost_usd > 0 else 0.0

    # Human-friendly display
    if run_cost_usd <= 0:
        return 0.0, "< 1 minute"

    if days < (1.0 / 24.0):  # < 1 hour
        minutes = max(1.0, days * 24.0 * 60.0)
        return days, f"~{minutes:,.0f} minutes"
    if days < 1.0:
        hours = days * 24.0
        return days, f"~{hours:,.1f} hours"
    if days < 30.0:
        return days, f"~{days:,.1f} days"
    months = days / 30.0
    return days, f"~{months:,.1f} months"


def _compute_roi_multiple(run_cost_usd: float, monthly_savings_usd: float) -> Tuple[Optional[float], str]:
    """
    Returns (roi_multiple, human_display_string).
    """
    if run_cost_usd <= 0:
        return None, "N/A"
    multiple = monthly_savings_usd / run_cost_usd if run_cost_usd > 0 else None
    if multiple is None:
        return None, "N/A"
    if multiple >= 1000:
        return multiple, f"{multiple:,.0f}×"
    if multiple >= 100:
        return multiple, f"{multiple:,.1f}×"
    return multiple, f"{multiple:,.2f}×"


@dataclass(frozen=True)
class ROIReport:
    title: str
    generated_at_utc: str
    correlation_id: str
    run_id: str

    decision_outcome: str
    decision_confidence: float

    estimated_monthly_savings_usd: float
    estimated_run_cost_usd: float
    roi_multiple_monthly: Optional[float]
    roi_multiple_display: str
    payback_days: Optional[float]
    payback_display: str

    num_resources_scanned: int
    num_recommendations: int
    risk_counts: Dict[str, int]

    top_recommendations: List[Dict[str, Any]]
    notes: List[str]

    def to_markdown(self) -> str:
        # Executive summary “BLUF”
        low = self.risk_counts.get("low", 0)
        med = self.risk_counts.get("med", 0)
        high = self.risk_counts.get("high", 0)

        lines: List[str] = []
        lines.append(f"# {self.title}")
        lines.append("")
        lines.append("> **Executive Summary:** This run generated actionable cloud cost-optimization recommendations with")
        lines.append(f"> projected savings of **{_fmt_usd(self.estimated_monthly_savings_usd)} / month**.")
        lines.append("")

        lines.append("## Overview")
        lines.append(f"- Generated (UTC): **{self.generated_at_utc}**")
        lines.append(f"- Correlation ID: `{self.correlation_id}`")
        lines.append(f"- Run ID: `{self.run_id}`")
        lines.append("")

        lines.append("## Governance Decision")
        lines.append(f"- Outcome: **{self.decision_outcome}**")
        lines.append(f"- Confidence score: **{self.decision_confidence:.3f}**")
        lines.append("")

        lines.append("## ROI Summary")
        lines.append(f"- Estimated monthly savings: **{_fmt_usd(self.estimated_monthly_savings_usd)}**")
        lines.append(f"- Estimated run cost: **{_fmt_usd(self.estimated_run_cost_usd, decimals=4)}**")
        lines.append(f"- ROI multiple (monthly savings / run cost): **{self.roi_multiple_display}**")
        lines.append(f"- Payback period: **{self.payback_display}**")
        lines.append("")

        lines.append("## Scope and Risk Breakdown")
        lines.append("")
        lines.append("| Metric | Value |")
        lines.append("|---|---:|")
        lines.append(f"| Resources scanned | {self.num_resources_scanned:,} |")
        lines.append(f"| Recommendations generated | {self.num_recommendations:,} |")
        lines.append(f"| Low risk | {low:,} |")
        lines.append(f"| Medium risk | {med:,} |")
        lines.append(f"| High risk | {high:,} |")
        if self.risk_counts.get("unknown", 0):
            lines.append(f"| Unknown risk | {self.risk_counts.get('unknown', 0):,} |")
        lines.append("")

        # A pragmatic “what would we do next” section
        lines.append("## Recommended Next Actions")
        if high > 0:
            lines.append(f"- **Owner approval required:** {high} high-risk recommendation(s) (e.g., database rightsizing).")
        if med > 0:
            lines.append(f"- **Validate before scheduling changes:** {med} medium-risk recommendation(s).")
        if low > 0:
            lines.append(f"- **Candidate for fast-track remediation:** {low} low-risk recommendation(s) (after standard checks).")
        if self.num_recommendations == 0:
            lines.append("- No actions recommended in this run.")
        lines.append("")

        lines.append("## Top Recommendations (by estimated monthly savings)")
        lines.append("")
        if not self.top_recommendations:
            lines.append("_No recommendations generated._")
            lines.append("")
        else:
            lines.append("| Rank | Resource | Type | Action | Risk | Est. savings / month | Rationale |")
            lines.append("|---:|---|---|---|---|---:|---|")
            for i, r in enumerate(self.top_recommendations, start=1):
                rid = str(r.get("resource_id", ""))
                rtype = str(r.get("resource_type", ""))
                action = str(r.get("action", ""))
                risk = str(r.get("risk", ""))
                savings = _safe_float(r.get("est_monthly_savings_usd", 0.0))
                rationale = str(r.get("rationale", "")).replace("\n", " ").strip()
                lines.append(
                    f"| {i} | `{rid}` | {rtype} | {action} | {risk} | {_fmt_usd(savings)} | {rationale} |"
                )
            lines.append("")

        if self.notes:
            lines.append("## Notes")
            for n in self.notes:
                lines.append(f"- {n}")
            lines.append("")

        return "\n".join(lines)


def build_finops_roi_report(
    *,
    sentinel_result: Any,
    agent_output: Dict[str, Any],
    title: str = "Agent Sentinel ROI Report (FinOps)",
    top_n: int = 5,
) -> ROIReport:
    generated_at_utc = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S")

    # Pull key metrics from agent output
    summary = agent_output.get("summary", {}) if isinstance(agent_output, dict) else {}
    recs = agent_output.get("recommendations", []) if isinstance(agent_output, dict) else []
    if not isinstance(recs, list):
        recs = []

    monthly_savings = _safe_float(summary.get("est_total_monthly_savings_usd", 0.0))
    num_scanned = int(_safe_float(summary.get("num_resources_scanned", 0), 0))
    num_recs = int(_safe_float(summary.get("num_recommendations", len(recs)), len(recs)))

    # True run cost tracked by SentinelRunner
    ctx_snapshot = getattr(sentinel_result, "ctx_snapshot", {}) or {}
    run_cost = _safe_float(ctx_snapshot.get("state", {}).get("cost_usd", 0.0))

    # Decision fields (clean formatting)
    outcome_obj = getattr(sentinel_result, "outcome", "")
    decision_outcome = getattr(outcome_obj, "value", str(outcome_obj))
    decision_conf = float(getattr(sentinel_result, "confidence", 0.0))

    # Sort recommendations: primarily by savings; tie-breaker by risk rank (prefer low risk first if equal)
    recs_sorted = sorted(
        recs,
        key=lambda r: (
            _safe_float(r.get("est_monthly_savings_usd", 0.0)),
            -_risk_rank(str(r.get("risk", ""))) * -1,  # keep deterministic, minor tie-break
        ),
        reverse=True,
    )
    top_recs = recs_sorted[:max(0, int(top_n))]

    roi_multiple, roi_display = _compute_roi_multiple(run_cost, monthly_savings)
    payback_days, payback_display = _compute_payback_display(run_cost, monthly_savings)
    risk_counts = _summarize_risks(recs)

    notes: List[str] = []
    notes.append("Savings are estimates based on deterministic heuristics in the demo (replace with real utilization + pricing data).")
    notes.append("High-risk actions (e.g., database rightsizing) should require explicit owner approval and a rollback plan.")
    notes.append("Audit events were recorded in an append-only log to support traceability and governance review.")

    return ROIReport(
        title=title,
        generated_at_utc=generated_at_utc,
        correlation_id=str(getattr(sentinel_result, "correlation_id", "")),
        run_id=str(getattr(sentinel_result, "run_id", "")),
        decision_outcome=decision_outcome,
        decision_confidence=decision_conf,
        estimated_monthly_savings_usd=monthly_savings,
        estimated_run_cost_usd=run_cost,
        roi_multiple_monthly=roi_multiple,
        roi_multiple_display=roi_display,
        payback_days=payback_days,
        payback_display=payback_display,
        num_resources_scanned=num_scanned,
        num_recommendations=num_recs,
        risk_counts=risk_counts,
        top_recommendations=top_recs,
        notes=notes,
    )
