"""Honest savings calculator -- conservative, transparent, never overstated.

Core principle: NEVER overstate savings.  Be conservative.
If in doubt, show the lower number.

Uses the :class:`~llmhost.router.cost.CostTracker` for actual request data and
an optional :class:`~llmhost.onboarding.manager.SpendingBaseline` for
comparison against the user's self-reported cloud spend.
"""

from __future__ import annotations

import logging
import math
from typing import TYPE_CHECKING

from pydantic import BaseModel, Field

if TYPE_CHECKING:
    from llmhosts.onboarding.manager import SpendingBaseline
    from llmhosts.router.cost import CostTracker

logger = logging.getLogger(__name__)

# ---------------------------------------------------------------------------
# Methodology statement -- shown to the user with every report
# ---------------------------------------------------------------------------

_METHODOLOGY = (
    "Savings = cloud equivalent pricing - actual cloud spend. "
    "Local inference is $0 (your hardware, your electricity). "
    "Cloud equivalent uses published per-token pricing. "
    "We never count electricity or hardware depreciation as a cost."
)

# How many days of data we need for each confidence tier
_CONFIDENCE_THRESHOLDS = {"low": 0, "medium": 7, "high": 30}

# Honesty score weights
_HONESTY_WEIGHT_HAS_DATA = 0.3
_HONESTY_WEIGHT_HAS_CLOUD_PRICING = 0.3
_HONESTY_WEIGHT_REASONABLE_SAVINGS = 0.2
_HONESTY_WEIGHT_HAS_BASELINE = 0.2


# ---------------------------------------------------------------------------
# Pydantic v2 report models
# ---------------------------------------------------------------------------


class SavingsReport(BaseModel):
    """Aggregate honest savings for a time period."""

    period: str
    actual_cost: float = 0.0
    cloud_equivalent: float = 0.0
    savings_amount: float = 0.0
    savings_percent: float = Field(default=0.0, description="Capped at 100.")
    local_requests: int = 0
    cloud_requests: int = 0
    cache_hits: int = 0
    total_requests: int = 0
    baseline_monthly: float | None = None
    vs_baseline_savings: float | None = None
    honesty_score: float = Field(default=0.0, ge=0.0, le=1.0)
    methodology: str = _METHODOLOGY


class RequestSavings(BaseModel):
    """Per-request savings breakdown."""

    request_id: str
    actual_cost: float = 0.0
    cloud_equivalent_cost: float = 0.0
    saved: float = 0.0
    routed_to: str = "local"
    model_used: str = ""
    routing_reason: str = ""
    cumulative_today: float = 0.0


class MonthlyProjection(BaseModel):
    """Projected monthly savings based on current usage patterns."""

    projected_requests: int = 0
    projected_cloud_cost: float = 0.0
    projected_actual_cost: float = 0.0
    projected_savings: float = 0.0
    projected_savings_percent: float = Field(default=0.0, description="Capped at 100.")
    confidence: str = "low"
    data_points: int = 0


class ValidationResult(BaseModel):
    """Result of validating a savings claim."""

    valid: bool = True
    issues: list[str] = Field(default_factory=list)
    corrected_savings: float | None = None


# ---------------------------------------------------------------------------
# HonestSavings calculator
# ---------------------------------------------------------------------------


class HonestSavings:
    """Calculates and validates savings claims.

    Core principle: NEVER overstate savings.  Be conservative.
    If in doubt, show the lower number.
    """

    def __init__(
        self,
        cost_tracker: CostTracker,
        baseline: SpendingBaseline | None = None,
    ) -> None:
        self._tracker = cost_tracker
        self._baseline = baseline

    # -- public properties ---------------------------------------------------

    @property
    def baseline(self) -> SpendingBaseline | None:
        return self._baseline

    @baseline.setter
    def baseline(self, value: SpendingBaseline | None) -> None:
        self._baseline = value

    # -- public API ----------------------------------------------------------

    async def calculate(self, period: str = "month") -> SavingsReport:
        """Calculate honest savings for a period.

        Rules:
        1. local_cost = $0 always (your hardware, your electricity not counted)
        2. cloud_equivalent = what this would cost on the cloud provider
        3. actual_cost = what was actually spent on cloud APIs this period
        4. savings = cloud_equivalent - actual_cost (if baseline set, compare to baseline)
        5. savings_percent = savings / cloud_equivalent * 100
        6. NEVER exceed 100% savings
        7. If no baseline, show savings vs cloud equivalent only
        8. If baseline set, show savings vs baseline spending
        """
        raw = await self._tracker.get_savings(period)

        cloud_equivalent = raw.cloud_equivalent_cost
        actual_cost = raw.actual_cost
        savings_amount = max(cloud_equivalent - actual_cost, 0.0)

        savings_percent = min(savings_amount / cloud_equivalent * 100.0, 100.0) if cloud_equivalent > 0 else 0.0

        # Baseline comparison
        baseline_monthly: float | None = None
        vs_baseline_savings: float | None = None
        if self._baseline is not None:
            baseline_monthly = self._baseline.monthly_estimate
            if baseline_monthly > 0 and period == "month":
                vs_baseline_savings = max(baseline_monthly - actual_cost, 0.0)

        honesty_score = self._compute_honesty_score(
            total_requests=raw.total_requests,
            cloud_equivalent=cloud_equivalent,
            savings_percent=savings_percent,
            has_baseline=self._baseline is not None,
        )

        return SavingsReport(
            period=period,
            actual_cost=round(actual_cost, 6),
            cloud_equivalent=round(cloud_equivalent, 6),
            savings_amount=round(savings_amount, 6),
            savings_percent=round(min(savings_percent, 100.0), 2),
            local_requests=raw.local_requests,
            cloud_requests=raw.cloud_requests,
            cache_hits=raw.cache_hits,
            total_requests=raw.total_requests,
            baseline_monthly=round(baseline_monthly, 2) if baseline_monthly is not None else None,
            vs_baseline_savings=round(vs_baseline_savings, 2) if vs_baseline_savings is not None else None,
            honesty_score=round(honesty_score, 2),
            methodology=_METHODOLOGY,
        )

    async def per_request_savings(self, request_id: str) -> RequestSavings:
        """Per-request savings breakdown.

        Shows:
        - What this request cost (local=$0, cloud=$X.XX)
        - What it WOULD have cost on cloud
        - Routing decision reason
        - Cumulative savings so far today
        """
        logs = await self._tracker.get_request_log(limit=1000)

        target = None
        for log in logs:
            if log.request_id == request_id:
                target = log
                break

        if target is None:
            return RequestSavings(request_id=request_id)

        saved = max(target.cloud_equivalent_cost - target.actual_cost, 0.0)

        # Determine routing type
        if target.cached:
            routed_to = "cache"
        elif target.backend_type.lower() in {"ollama", "vllm", "exo", "local"}:
            routed_to = "local"
        else:
            routed_to = "cloud"

        # Cumulative savings for today
        today_report = await self._tracker.get_savings("day")
        cumulative_today = max(today_report.cloud_equivalent_cost - today_report.actual_cost, 0.0)

        return RequestSavings(
            request_id=request_id,
            actual_cost=round(target.actual_cost, 6),
            cloud_equivalent_cost=round(target.cloud_equivalent_cost, 6),
            saved=round(saved, 6),
            routed_to=routed_to,
            model_used=target.model_used,
            routing_reason=target.routing_reasoning,
            cumulative_today=round(cumulative_today, 6),
        )

    def validate_claim(self, claimed_savings: float, period: str) -> ValidationResult:
        """Validate a savings claim is honest.

        Checks:
        - Is savings_percent <= 100%?
        - Is the cloud equivalent price realistic?
        - Is the comparison period fair?
        - Is the baseline accurate?
        """
        issues: list[str] = []
        corrected: float | None = None

        # Rule 1: savings percent can never exceed 100%
        if claimed_savings > 100.0:
            issues.append(f"Savings of {claimed_savings:.1f}% exceeds 100% -- impossible.")
            corrected = 100.0

        # Rule 2: negative savings don't make sense as a claim
        if claimed_savings < 0.0:
            issues.append("Negative savings percentage is not a valid claim.")
            corrected = 0.0

        # Rule 3: unrealistically high for very short periods
        if period == "day" and claimed_savings > 99.0:
            issues.append("Single-day data may not be representative; savings may be inflated.")

        # Rule 4: if baseline exists, check consistency
        if self._baseline is not None and self._baseline.monthly_estimate <= 0:
            issues.append("Baseline monthly estimate is $0 -- savings comparison is meaningless.")

        valid = len(issues) == 0
        return ValidationResult(valid=valid, issues=issues, corrected_savings=corrected)

    async def monthly_projection(self) -> MonthlyProjection:
        """Project monthly savings based on current usage patterns.

        Uses:
        - Current usage rate (requests/day)
        - Average cost per request
        - Local vs cloud ratio
        - Extrapolates to monthly
        """
        # Gather data for different periods to determine confidence
        day_report = await self._tracker.get_savings("day")
        week_report = await self._tracker.get_savings("week")
        month_report = await self._tracker.get_savings("month")

        # Determine best data source and confidence
        if month_report.total_requests >= 30:
            confidence = "high"
            data_points = month_report.total_requests
            base_report = month_report
            # Month data: scale up from whatever fraction of 30 days we have
            scale_factor = 1.0  # already ~monthly
        elif week_report.total_requests >= 7:
            confidence = "medium"
            data_points = week_report.total_requests
            base_report = week_report
            scale_factor = 30.0 / 7.0  # extrapolate week to month
        elif day_report.total_requests > 0:
            confidence = "low"
            data_points = day_report.total_requests
            base_report = day_report
            scale_factor = 30.0  # extrapolate one day to month
        else:
            return MonthlyProjection(confidence="low", data_points=0)

        projected_requests = max(1, math.ceil(base_report.total_requests * scale_factor))
        projected_cloud_cost = base_report.cloud_equivalent_cost * scale_factor
        projected_actual_cost = base_report.actual_cost * scale_factor
        projected_savings = max(projected_cloud_cost - projected_actual_cost, 0.0)

        if projected_cloud_cost > 0:
            projected_savings_percent = min(projected_savings / projected_cloud_cost * 100.0, 100.0)
        else:
            projected_savings_percent = 0.0

        return MonthlyProjection(
            projected_requests=projected_requests,
            projected_cloud_cost=round(projected_cloud_cost, 6),
            projected_actual_cost=round(projected_actual_cost, 6),
            projected_savings=round(projected_savings, 6),
            projected_savings_percent=round(projected_savings_percent, 2),
            confidence=confidence,
            data_points=data_points,
        )

    # -- internal helpers ----------------------------------------------------

    @staticmethod
    def _compute_honesty_score(
        *,
        total_requests: int,
        cloud_equivalent: float,
        savings_percent: float,
        has_baseline: bool,
    ) -> float:
        """Compute a 0-1 honesty confidence score.

        Higher = more confident the numbers are accurate.

        Factors:
        - Has actual data (requests > 0)
        - Has cloud pricing for comparison
        - Savings are in a reasonable range
        - Has user baseline for cross-validation
        """
        score = 0.0

        # Factor 1: we have real data
        if total_requests > 0:
            score += _HONESTY_WEIGHT_HAS_DATA
        elif total_requests == 0:
            # No data at all -- very low confidence
            return 0.0

        # Factor 2: cloud pricing available
        if cloud_equivalent > 0:
            score += _HONESTY_WEIGHT_HAS_CLOUD_PRICING

        # Factor 3: savings are in a reasonable range (0-100%)
        if 0.0 <= savings_percent <= 100.0:
            score += _HONESTY_WEIGHT_REASONABLE_SAVINGS

        # Factor 4: baseline available for cross-validation
        if has_baseline:
            score += _HONESTY_WEIGHT_HAS_BASELINE

        return min(score, 1.0)
