"""Report generation for cost savings analysis.

Pure functions that build report data from OutcomeDB queries.
Formatting is separate from data generation — the ReportData model
holds structured data, and format_* functions produce output strings.
"""

from __future__ import annotations

import json
from datetime import datetime, timedelta, timezone

from pydantic import BaseModel, Field

from robot_resources.tracking.db import OutcomeDB


class TaskCostBreakdown(BaseModel):
    """Cost savings for a single task type."""

    task_type: str
    request_count: int
    cost_saved: float

    model_config = {"frozen": True}


class ModelUsage(BaseModel):
    """Request count for a single model."""

    model: str
    request_count: int

    model_config = {"frozen": True}


class ReportData(BaseModel):
    """Structured report data for a time period.

    Holds all data needed for both table and JSON output.
    Generated by ``build_report()``, formatted by ``format_report_*()``.
    """

    period: str
    generated_at: datetime
    date_from: datetime
    date_to: datetime

    total_requests: int = 0
    total_cost_saved: float = 0.0
    total_cost_actual: float = 0.0
    total_cost_baseline: float = 0.0
    avg_savings_per_request: float = 0.0
    avg_savings_percent: float = 0.0
    success_rate: float | None = None

    cost_by_task_type: list[TaskCostBreakdown] = Field(default_factory=list)
    top_models: list[ModelUsage] = Field(default_factory=list)

    model_config = {"frozen": True}


# ------------------------------------------------------------------
# Data generation
# ------------------------------------------------------------------

_PERIOD_DAYS = {"weekly": 7, "monthly": 30}


def build_report(db: OutcomeDB, period: str = "weekly") -> ReportData:
    """Build a cost savings report for the given period.

    Queries the database for the specified time window and assembles
    structured report data. The DB must be connected.

    Args:
        db: Connected OutcomeDB instance.
        period: ``"weekly"`` (7 days) or ``"monthly"`` (30 days).

    Returns:
        Frozen ReportData with all fields populated.
    """
    now = datetime.now(timezone.utc)
    days = _PERIOD_DAYS.get(period, 7)
    since = now - timedelta(days=days)

    summary = db.get_summary(since=since)

    # Cost breakdown by task type
    cost_by_task_raw = db.get_cost_by_task_type(since=since)
    cost_breakdowns = sorted(
        [
            TaskCostBreakdown(
                task_type=task,
                request_count=int(data["count"]),
                cost_saved=float(data["cost_saved"]),
            )
            for task, data in cost_by_task_raw.items()
        ],
        key=lambda b: b.cost_saved,
        reverse=True,
    )

    # Top 5 models by request count
    sorted_models = sorted(
        summary.by_model.items(), key=lambda x: x[1], reverse=True
    )[:5]
    top_models = [
        ModelUsage(model=m, request_count=c) for m, c in sorted_models
    ]

    # Average savings per request
    avg_per_request = (
        summary.total_cost_saved / summary.total_requests
        if summary.total_requests > 0
        else 0.0
    )

    return ReportData(
        period=period,
        generated_at=now,
        date_from=since,
        date_to=now,
        total_requests=summary.total_requests,
        total_cost_saved=summary.total_cost_saved,
        total_cost_actual=summary.total_cost_actual,
        total_cost_baseline=summary.total_cost_baseline,
        avg_savings_per_request=round(avg_per_request, 6),
        avg_savings_percent=summary.avg_savings_percent,
        success_rate=summary.success_rate,
        cost_by_task_type=cost_breakdowns,
        top_models=top_models,
    )


# ------------------------------------------------------------------
# Table formatting
# ------------------------------------------------------------------


def format_report_table(report: ReportData) -> str:
    """Format report as a human-readable table.

    Includes premium upsell CTA at the bottom.
    """
    label = report.period.capitalize()
    date_from = report.date_from.strftime("%Y-%m-%d")
    date_to = report.date_to.strftime("%Y-%m-%d")

    lines: list[str] = []
    lines.append("")
    lines.append(f"  Router {label} Cost Report")
    lines.append(f"  {date_from} -> {date_to}")
    lines.append("  " + "-" * 44)

    if report.total_requests == 0:
        lines.append("")
        lines.append("  No data for this period.")
        lines.append("")
        lines.append(_premium_cta())
        return "\n".join(lines)

    # Summary
    lines.append("")
    lines.append(f"  Total Requests:      {report.total_requests:,}")
    lines.append(f"  Total Cost Saved:    ${report.total_cost_saved:.2f}")
    lines.append(f"  Actual Cost:         ${report.total_cost_actual:.2f}")
    lines.append(f"  Baseline Cost:       ${report.total_cost_baseline:.2f}")
    lines.append(
        f"  Avg Saved/Request:   ${report.avg_savings_per_request:.4f}"
    )
    lines.append(f"  Avg Savings:         {report.avg_savings_percent:.1f}%")

    if report.success_rate is not None:
        lines.append(f"  Success Rate:        {report.success_rate * 100:.1f}%")
    else:
        lines.append("  Success Rate:        N/A")

    # Task type breakdown
    if report.cost_by_task_type:
        lines.append("")
        lines.append("  Cost Saved by Task Type")
        lines.append("  " + "-" * 40)
        for b in report.cost_by_task_type:
            pct = (
                (b.cost_saved / report.total_cost_saved * 100)
                if report.total_cost_saved > 0
                else 0.0
            )
            lines.append(
                f"  {b.task_type:<18} ${b.cost_saved:.4f}  "
                f"({pct:.1f}%, {b.request_count} reqs)"
            )

    # Top models
    if report.top_models:
        lines.append("")
        lines.append("  Top Models (by request count)")
        lines.append("  " + "-" * 40)
        for i, m in enumerate(report.top_models, 1):
            lines.append(
                f"  {i}. {m.model:<22} {m.request_count:>5} requests"
            )

    lines.append("")
    lines.append(_premium_cta())

    return "\n".join(lines)


def _premium_cta() -> str:
    """Return the premium upsell call-to-action text."""
    return (
        "  -------------------------------------------------\n"
        "  Unlock global learning: See how other users\n"
        "  optimize routing.\n"
        "  Learn more: https://robotresources.dev/premium\n"
        "  -------------------------------------------------"
    )


# ------------------------------------------------------------------
# JSON formatting
# ------------------------------------------------------------------


def format_report_json(report: ReportData) -> str:
    """Format report as machine-readable JSON.

    Excludes generated_at, date_from, date_to datetime objects
    and serialises them as ISO strings. No premium CTA.
    """
    data = {
        "period": report.period,
        "generated_at": report.generated_at.isoformat(),
        "date_from": report.date_from.isoformat(),
        "date_to": report.date_to.isoformat(),
        "total_requests": report.total_requests,
        "total_cost_saved": report.total_cost_saved,
        "total_cost_actual": report.total_cost_actual,
        "total_cost_baseline": report.total_cost_baseline,
        "avg_savings_per_request": report.avg_savings_per_request,
        "avg_savings_percent": report.avg_savings_percent,
        "success_rate": report.success_rate,
        "cost_by_task_type": [
            {
                "task_type": b.task_type,
                "request_count": b.request_count,
                "cost_saved": b.cost_saved,
            }
            for b in report.cost_by_task_type
        ],
        "top_models": [
            {"model": m.model, "request_count": m.request_count}
            for m in report.top_models
        ],
    }
    return json.dumps(data, indent=2)
