"""Models for code analysis and tech debt tracking."""

from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum
from pathlib import Path


class ImpactLevel(str, Enum):
    """Impact level for analysis findings."""

    LOW = "low"
    MEDIUM = "medium"
    HIGH = "high"
    CRITICAL = "critical"


class AnalysisCategory(str, Enum):
    """Categories of analysis."""

    COMPLEXITY = "complexity"
    QUALITY = "quality"
    SECURITY = "security"
    MAINTAINABILITY = "maintainability"
    SMELL = "smell"


@dataclass
class AnalysisFinding:
    """A single finding from an analyzer."""

    category: AnalysisCategory
    message: str
    file_path: str | None = None
    line_number: int | None = None
    impact: ImpactLevel = ImpactLevel.MEDIUM
    rule_id: str | None = None
    suggestion: str | None = None


@dataclass
class FileMetrics:
    """Metrics for a single file."""

    path: str
    lines_of_code: int = 0
    cyclomatic_complexity: float = 0.0
    cognitive_complexity: float = 0.0
    maintainability_index: float = 100.0
    warning_count: int = 0
    error_count: int = 0


@dataclass
class AnalysisMetrics:
    """Aggregated metrics from analysis."""

    # Complexity
    avg_cyclomatic_complexity: float = 0.0
    max_cyclomatic_complexity: float = 0.0
    avg_cognitive_complexity: float = 0.0
    max_cognitive_complexity: float = 0.0

    # Quality
    total_warnings: int = 0
    total_errors: int = 0
    warnings_by_category: dict[str, int] = field(default_factory=dict)

    # Security
    security_issues: int = 0
    security_by_severity: dict[str, int] = field(default_factory=dict)

    # Maintainability
    avg_maintainability_index: float = 100.0
    min_maintainability_index: float = 100.0

    # Coverage
    files_analyzed: int = 0
    total_lines: int = 0


@dataclass
class AnalysisResult:
    """Result of running analysis."""

    timestamp: datetime = field(default_factory=datetime.now)
    metrics: AnalysisMetrics = field(default_factory=AnalysisMetrics)
    findings: list[AnalysisFinding] = field(default_factory=list)
    file_metrics: list[FileMetrics] = field(default_factory=list)
    analyzed_paths: list[str] = field(default_factory=list)
    git_ref: str | None = None

    def get_findings_by_category(self, category: AnalysisCategory) -> list[AnalysisFinding]:
        """Get findings filtered by category."""
        return [f for f in self.findings if f.category == category]

    def get_critical_findings(self) -> list[AnalysisFinding]:
        """Get all critical and high impact findings."""
        return [f for f in self.findings if f.impact in (ImpactLevel.CRITICAL, ImpactLevel.HIGH)]


@dataclass
class MetricsDelta:
    """Change in a metric between before and after."""

    metric_name: str
    before: float
    after: float
    absolute_change: float
    percent_change: float
    is_regression: bool

    @classmethod
    def calculate(
        cls,
        name: str,
        before: float,
        after: float,
        higher_is_worse: bool = True,
    ) -> "MetricsDelta":
        """Calculate delta between two values."""
        absolute = after - before
        percent = ((after - before) / before * 100) if before != 0 else 0.0
        is_regression = (absolute > 0) if higher_is_worse else (absolute < 0)

        return cls(
            metric_name=name,
            before=before,
            after=after,
            absolute_change=absolute,
            percent_change=percent,
            is_regression=is_regression,
        )


@dataclass
class AnalysisComparison:
    """Comparison between two analysis results."""

    before: AnalysisResult
    after: AnalysisResult
    deltas: list[MetricsDelta] = field(default_factory=list)
    new_findings: list[AnalysisFinding] = field(default_factory=list)
    resolved_findings: list[AnalysisFinding] = field(default_factory=list)

    @property
    def has_regressions(self) -> bool:
        """Check if any metrics regressed."""
        return any(d.is_regression for d in self.deltas)

    @property
    def overall_impact(self) -> str:
        """Determine overall impact level."""
        critical_regressions = sum(
            1 for d in self.deltas if d.is_regression and abs(d.percent_change) > 20
        )
        moderate_regressions = sum(
            1 for d in self.deltas if d.is_regression and 5 < abs(d.percent_change) <= 20
        )

        if critical_regressions > 0 or len(self.new_findings) > 10:
            return "high_concern"
        elif moderate_regressions > 0 or len(self.new_findings) > 5:
            return "moderate_concern"
        elif self.has_regressions:
            return "minor_concern"
        else:
            return "acceptable"

    def calculate_deltas(self) -> None:
        """Calculate all metric deltas."""
        b = self.before.metrics
        a = self.after.metrics

        self.deltas = [
            MetricsDelta.calculate(
                "avg_cyclomatic_complexity",
                b.avg_cyclomatic_complexity,
                a.avg_cyclomatic_complexity,
                higher_is_worse=True,
            ),
            MetricsDelta.calculate(
                "avg_cognitive_complexity",
                b.avg_cognitive_complexity,
                a.avg_cognitive_complexity,
                higher_is_worse=True,
            ),
            MetricsDelta.calculate(
                "total_warnings",
                float(b.total_warnings),
                float(a.total_warnings),
                higher_is_worse=True,
            ),
            MetricsDelta.calculate(
                "security_issues",
                float(b.security_issues),
                float(a.security_issues),
                higher_is_worse=True,
            ),
            MetricsDelta.calculate(
                "avg_maintainability_index",
                b.avg_maintainability_index,
                a.avg_maintainability_index,
                higher_is_worse=False,  # Higher MI is better
            ),
        ]

        # Calculate new and resolved findings
        before_keys = {(f.file_path, f.line_number, f.rule_id) for f in self.before.findings}
        after_keys = {(f.file_path, f.line_number, f.rule_id) for f in self.after.findings}

        new_keys = after_keys - before_keys
        resolved_keys = before_keys - after_keys

        self.new_findings = [
            f for f in self.after.findings if (f.file_path, f.line_number, f.rule_id) in new_keys
        ]
        self.resolved_findings = [
            f
            for f in self.before.findings
            if (f.file_path, f.line_number, f.rule_id) in resolved_keys
        ]


@dataclass
class AnalysisConfig:
    """Configuration for analysis."""

    # Paths
    paths: list[Path] = field(default_factory=lambda: [Path(".")])
    exclude_patterns: list[str] = field(
        default_factory=lambda: [
            "**/node_modules/**",
            "**/.venv/**",
            "**/venv/**",
            "**/__pycache__/**",
        ]
    )

    # Analyzer toggles
    enable_complexity: bool = True
    enable_quality: bool = True
    enable_security: bool = False  # Requires bandit
    enable_maintainability: bool = True

    # Thresholds
    max_cyclomatic_complexity: float = 10.0
    max_cognitive_complexity: float = 15.0
    min_maintainability_index: float = 20.0
    max_warnings_increase_percent: float = 10.0

    # Output
    fail_on_regression: bool = False
    report_format: str = "console"  # console, yaml, json, markdown
