"""Analyzer for comparing scan results."""

from apiposture.core.models.scan_result import ScanResult

from apiposture_pro.features.diff_mode.models import DiffResult, SeverityChange

# Severity ordering for escalation/de-escalation detection
_SEVERITY_ORDER = {
    "info": 0,
    "low": 1,
    "medium": 2,
    "high": 3,
    "critical": 4,
}


class DiffAnalyzer:
    """Analyzes differences between two scan results."""

    def __init__(self) -> None:
        """Initialize diff analyzer."""
        self.regression_severities = {"critical", "high"}

    def compare(self, baseline: ScanResult, current: ScanResult) -> DiffResult:
        """
        Compare two scan results and identify changes.

        Args:
            baseline: Baseline scan result
            current: Current scan result

        Returns:
            DiffResult with detected changes
        """
        result = DiffResult(
            baseline_scan_count=len(baseline.findings),
            current_scan_count=len(current.findings),
        )

        # Compare findings (including severity changes)
        self._compare_findings(baseline, current, result)

        # Compare endpoints
        self._compare_endpoints(baseline, current, result)

        # Detect regressions
        self._detect_regressions(result)

        return result

    def _compare_findings(
        self, baseline: ScanResult, current: ScanResult, result: DiffResult
    ) -> None:
        """Compare findings between baseline and current scans."""
        # Location-based signature (without severity) for matching severity changes
        baseline_loc_sigs: dict[str, list] = {}
        for f in baseline.findings:
            loc_sig = self._location_signature(f)
            baseline_loc_sigs.setdefault(loc_sig, []).append(f)

        current_loc_sigs: dict[str, list] = {}
        for f in current.findings:
            loc_sig = self._location_signature(f)
            current_loc_sigs.setdefault(loc_sig, []).append(f)

        # Track which findings have been matched
        matched_baseline_locs = set()
        matched_current_locs = set()

        # First pass: find severity changes (same location, different severity)
        for loc_sig in baseline_loc_sigs:
            if loc_sig in current_loc_sigs:
                for b_finding in baseline_loc_sigs[loc_sig]:
                    for c_finding in current_loc_sigs[loc_sig]:
                        b_sev = self._severity_value(b_finding.severity)
                        c_sev = self._severity_value(c_finding.severity)
                        if b_sev != c_sev:
                            b_order = _SEVERITY_ORDER.get(b_sev, -1)
                            c_order = _SEVERITY_ORDER.get(c_sev, -1)
                            result.severity_changes.append(
                                SeverityChange(
                                    finding=c_finding,
                                    previous_severity=b_finding.severity,
                                    current_severity=c_finding.severity,
                                    is_escalation=c_order > b_order,
                                    is_deescalation=c_order < b_order,
                                )
                            )
                            matched_baseline_locs.add((loc_sig, id(b_finding)))
                            matched_current_locs.add((loc_sig, id(c_finding)))
                        else:
                            # Same severity = unchanged
                            result.unchanged_findings.append(c_finding)
                            matched_baseline_locs.add((loc_sig, id(b_finding)))
                            matched_current_locs.add((loc_sig, id(c_finding)))

        # Second pass: unmatched current findings are new
        for loc_sig, findings in current_loc_sigs.items():
            for f in findings:
                if (loc_sig, id(f)) not in matched_current_locs:
                    result.new_findings.append(f)

        # Third pass: unmatched baseline findings are resolved
        for loc_sig, findings in baseline_loc_sigs.items():
            for f in findings:
                if (loc_sig, id(f)) not in matched_baseline_locs:
                    result.resolved_findings.append(f)

    def _compare_endpoints(
        self, baseline: ScanResult, current: ScanResult, result: DiffResult
    ) -> None:
        """Compare endpoints between baseline and current scans."""
        def _method_str(method) -> str:
            return (method.value if hasattr(method, "value") else str(method)).upper()

        baseline_eps = {
            f"{_method_str(method)} {ep.route}"
            for ep in baseline.endpoints
            if hasattr(ep, "route") and hasattr(ep, "methods")
            for method in ep.methods
        }
        current_eps = {
            f"{_method_str(method)} {ep.route}"
            for ep in current.endpoints
            if hasattr(ep, "route") and hasattr(ep, "methods")
            for method in ep.methods
        }

        # New endpoints
        result.new_endpoints = sorted(current_eps - baseline_eps)

        # Removed endpoints
        result.removed_endpoints = sorted(baseline_eps - current_eps)

    def _detect_regressions(self, result: DiffResult) -> None:
        """Detect if there are any security regressions."""
        # Count new critical/high findings as regressions
        regression_count = sum(
            1 for f in result.new_findings if self._severity_value(f.severity) in self.regression_severities
        )

        # Count severity escalations to critical/high as regressions
        regression_count += sum(
            1 for sc in result.severity_changes
            if sc.is_escalation and self._severity_value(sc.current_severity) in self.regression_severities
        )

        result.has_regressions = regression_count > 0
        result.regression_count = regression_count

    def _location_signature(self, finding) -> str:
        """
        Generate a location-based signature for a finding (without severity).

        Used to match findings across scans for severity change detection.
        """
        endpoint_sig = ""
        if hasattr(finding, "endpoint") and finding.endpoint:
            endpoint_sig = f"{finding.endpoint.route}:{finding.endpoint.line_number}"

        return f"{finding.rule_id}|{endpoint_sig}"

    def _finding_signature(self, finding) -> str:
        """
        Generate a unique signature for a finding (including severity).
        """
        endpoint_sig = ""
        if hasattr(finding, "endpoint") and finding.endpoint:
            endpoint_sig = f"{finding.endpoint.route}:{finding.endpoint.line_number}"

        parts = [
            finding.rule_id,
            str(finding.severity),
            endpoint_sig,
        ]
        return "|".join(str(p) for p in parts)

    def _severity_value(self, severity: object) -> str:
        """Extract string value from severity (handles both enum and string)."""
        if hasattr(severity, "value"):
            return str(severity.value)
        return str(severity).lower()
