"""Estimate the dollar cost of technical debt.

Translates technical debt metrics into estimated engineering hours
and dollar costs using industry benchmarks and configurable rates.
"""

from __future__ import annotations

from dataclasses import dataclass
from typing import Any


@dataclass
class CostEstimate:
    """Cost estimate for technical debt in a file or module."""

    file_path: str
    monthly_hours_wasted: float
    monthly_cost_usd: float
    annual_cost_usd: float
    fix_effort_hours: float
    fix_cost_usd: float
    roi_months: float  # Payback period in months
    breakdown: dict[str, float]

    def to_dict(self) -> dict[str, Any]:
        return {
            "file_path": self.file_path,
            "monthly_hours_wasted": round(self.monthly_hours_wasted, 1),
            "monthly_cost_usd": round(self.monthly_cost_usd, 2),
            "annual_cost_usd": round(self.annual_cost_usd, 2),
            "fix_effort_hours": round(self.fix_effort_hours, 1),
            "fix_cost_usd": round(self.fix_cost_usd, 2),
            "roi_months": round(self.roi_months, 1),
            "breakdown": {k: round(v, 2) for k, v in self.breakdown.items()},
        }


class CostEstimator:
    """Estimate dollar cost of technical debt.

    Uses research-backed estimates:
    - High complexity functions take 2-5x longer to modify (Halstead, McCabe)
    - Duplicated code multiplies bug-fix effort linearly
    - Missing tests increase defect escape rate by ~40% (Capers Jones)
    - Each production incident costs ~4 hours of engineering time on average
    """

    def __init__(
        self,
        hourly_rate_usd: float = 75.0,
        team_size: int = 5,
        avg_monthly_changes_per_file: float = 2.0,
    ) -> None:
        """Initialize cost estimator.

        Args:
            hourly_rate_usd: Average fully-loaded engineer hourly rate.
            team_size: Number of engineers on the team.
            avg_monthly_changes_per_file: Average times a file is modified per month.
        """
        self.hourly_rate = hourly_rate_usd
        self.team_size = team_size
        self.avg_changes = avg_monthly_changes_per_file

    def estimate_file(
        self,
        file_path: str,
        complexity_score: float = 0,
        duplication_count: int = 0,
        test_coverage_gap: float = 0,
        coupling_score: float = 0,
        incident_count: int = 0,
        line_count: int = 100,
    ) -> CostEstimate:
        """Estimate cost of technical debt for a single file.

        Args:
            file_path: Path to the file.
            complexity_score: Average cyclomatic complexity (raw, not normalized).
            duplication_count: Number of duplicate blocks.
            test_coverage_gap: Percentage of untested public APIs (0-100).
            coupling_score: Total coupling count.
            incident_count: Monthly incidents linked to this file.
            line_count: Total lines in the file.
        """
        breakdown: dict[str, float] = {}

        # Complexity cost: high complexity = longer comprehension time per change
        # Base: 0.5 hours per change. Complexity multiplier: 1 + (score-5)/10
        complexity_multiplier = max(1.0, 1.0 + (complexity_score - 5.0) / 10.0)
        base_change_hours = 0.5
        complexity_overhead = (complexity_multiplier - 1.0) * base_change_hours * self.avg_changes
        breakdown["complexity_overhead"] = complexity_overhead

        # Duplication cost: each duplicate block = extra bug-fix propagation time
        # ~0.25 hours per duplicate per change
        duplication_overhead = duplication_count * 0.25 * self.avg_changes
        breakdown["duplication_overhead"] = duplication_overhead

        # Test gap cost: missing tests increase defect escape rate
        # Each escaped defect costs ~4 hours on average
        # Probability of defect per change: test_gap% * 0.15
        defect_probability = (test_coverage_gap / 100.0) * 0.15
        defect_cost = defect_probability * 4.0 * self.avg_changes
        breakdown["test_gap_cost"] = defect_cost

        # Coupling cost: high coupling = ripple effect on changes
        # 0.1 hours per coupled module per change
        coupling_overhead = min(coupling_score, 20) * 0.1 * self.avg_changes
        breakdown["coupling_overhead"] = coupling_overhead

        # Incident cost: each incident = ~4 hours investigation + fix
        incident_cost_hours = incident_count * 4.0
        breakdown["incident_cost"] = incident_cost_hours

        # Total monthly waste
        monthly_hours = sum(breakdown.values())
        monthly_cost = monthly_hours * self.hourly_rate
        annual_cost = monthly_cost * 12

        # Fix effort estimation
        fix_hours = self._estimate_fix_effort(
            complexity_score, duplication_count, test_coverage_gap,
            coupling_score, line_count
        )
        fix_cost = fix_hours * self.hourly_rate

        # ROI: how many months until fix pays for itself
        roi_months = fix_cost / monthly_cost if monthly_cost > 0 else float("inf")

        return CostEstimate(
            file_path=file_path,
            monthly_hours_wasted=monthly_hours,
            monthly_cost_usd=monthly_cost,
            annual_cost_usd=annual_cost,
            fix_effort_hours=fix_hours,
            fix_cost_usd=fix_cost,
            roi_months=min(roi_months, 999.0),
            breakdown=breakdown,
        )

    def _estimate_fix_effort(
        self,
        complexity_score: float,
        duplication_count: int,
        test_coverage_gap: float,
        coupling_score: float,
        line_count: int,
    ) -> float:
        """Estimate hours needed to fix the debt."""
        hours = 0.0

        # Refactor complex functions: ~2 hours per complex function
        complex_function_count = max(0, complexity_score - 10) / 5
        hours += complex_function_count * 2.0

        # De-duplicate: ~1 hour per duplicate block
        hours += duplication_count * 1.0

        # Write missing tests: ~0.5 hours per uncovered function
        uncovered_count = (test_coverage_gap / 100.0) * (line_count / 20)  # rough estimate
        hours += uncovered_count * 0.5

        # Decouple: ~3 hours per highly-coupled module
        if coupling_score > 10:
            hours += 3.0

        return max(1.0, hours)  # Minimum 1 hour

    def estimate_project(
        self, file_estimates: list[CostEstimate]
    ) -> dict[str, Any]:
        """Aggregate cost estimates across a project."""
        if not file_estimates:
            return {
                "total_monthly_cost_usd": 0,
                "total_annual_cost_usd": 0,
                "total_fix_cost_usd": 0,
                "avg_roi_months": 0,
                "worst_files": [],
            }

        total_monthly = sum(e.monthly_cost_usd for e in file_estimates)
        total_annual = sum(e.annual_cost_usd for e in file_estimates)
        total_fix = sum(e.fix_cost_usd for e in file_estimates)

        valid_rois = [e.roi_months for e in file_estimates if e.roi_months < 999]
        avg_roi = sum(valid_rois) / len(valid_rois) if valid_rois else 0

        worst = sorted(file_estimates, key=lambda e: e.annual_cost_usd, reverse=True)[:10]

        return {
            "total_monthly_cost_usd": round(total_monthly, 2),
            "total_annual_cost_usd": round(total_annual, 2),
            "total_fix_cost_usd": round(total_fix, 2),
            "avg_roi_months": round(avg_roi, 1),
            "team_capacity_waste_pct": round(
                (total_monthly / (self.team_size * 160 * self.hourly_rate)) * 100, 1
            ) if self.team_size > 0 else 0,
            "worst_files": [e.to_dict() for e in worst],
        }
