"""
Cleaning Phase with LLM Enhancement.

Generates code quality improvements and refactoring suggestions.
Uses LLM to provide intelligent code cleaning recommendations.
"""

from dataclasses import dataclass
from pathlib import Path
from typing import Any

from warden.cleaning.application.llm_suggestion_generator import LLMSuggestionGenerator
from warden.cleaning.application.pattern_analyzer import PatternAnalyzer
from warden.cleaning.domain.models import Cleaning
from warden.shared.infrastructure.ignore_matcher import IgnoreMatcher
from warden.shared.infrastructure.logging import get_logger
from warden.validation.domain.frame import CodeFile

# Try to import LLMService, use None if not available
try:
    from warden.shared.services import LLMService
except ImportError:
    LLMService = None

logger = get_logger(__name__)


@dataclass
class CleaningPhaseResult:
    """Result from cleaning phase execution."""

    cleaning_suggestions: list[dict[str, Any]]
    refactorings: list[dict[str, Any]]
    quality_score_after: float
    code_improvements: dict[str, Any]
    confidence: float = 0.0


class CleaningPhase:
    """
    Phase 5: CLEANING - Generate code quality improvements.

    Responsibilities:
    - Analyze code for quality issues
    - Suggest refactorings
    - Remove dead code
    - Improve naming and structure
    - Optimize performance
    """

    def __init__(
        self,
        config: dict[str, Any] | None = None,
        context: dict[str, Any] | None = None,
        llm_service: LLMService | None = None,
        semantic_search_service: Any | None = None,
        rate_limiter: Any | None = None,
    ):
        """
        Initialize cleaning phase.

        Args:
            config: Phase configuration
            context: Pipeline context from previous phases
            llm_service: Optional LLM service for enhanced suggestions
        """
        self.config = config or {}
        self.context = context or {}
        self.llm_service = llm_service
        self.semantic_search_service = semantic_search_service
        self.rate_limiter = rate_limiter
        self.use_llm = self.config.get("use_llm", True) and llm_service is not None

        # Initialize IgnoreMatcher
        project_root = getattr(self.context, "project_root", None) or Path.cwd()
        if isinstance(self.context, dict):
            project_root = self.context.get("project_root") or project_root
            use_gitignore = self.context.get("use_gitignore", True)
        else:
            use_gitignore = getattr(self.context, "use_gitignore", True)

        self.ignore_matcher = IgnoreMatcher(Path(project_root), use_gitignore=use_gitignore)

        # Initialize analyzers
        self.pattern_analyzer = PatternAnalyzer()

        if self.use_llm:
            self.llm_generator = LLMSuggestionGenerator(
                llm_service=llm_service,
                context=context,
                semantic_search_service=semantic_search_service,
                rate_limiter=rate_limiter,
            )
        else:
            self.llm_generator = None

        logger.info(
            "cleaning_phase_initialized",
            use_llm=self.use_llm,
            context_keys=list(context.keys()) if context else [],
        )

    async def execute_async(
        self,
        code_files: list[CodeFile],
    ) -> CleaningPhaseResult:
        """
        Execute cleaning phase.
        """
        # Use debug level for phase start if no files to avoid CI noise
        log_func = logger.info if code_files else logger.debug
        log_func(
            "cleaning_phase_started",
            file_count=len(code_files),
            use_llm=self.use_llm,
        )

        # Filter files based on ignore matcher
        original_count = len(code_files)
        code_files = [
            cf for cf in code_files if not self.ignore_matcher.should_ignore_for_frame(Path(cf.path), "cleaning")
        ]

        if len(code_files) < original_count:
            logger.info(
                "cleaning_phase_files_ignored", ignored=original_count - len(code_files), remaining=len(code_files)
            )

        from warden.cleaning.application.orchestrator import CleaningOrchestrator

        orchestrator = CleaningOrchestrator()

        all_cleanings = []
        all_refactorings = []
        all_suggestions = []

        files_for_llm = []
        rule_based_suggestions = {}

        # Get critical files from context (Tier 2: Context-Aware Cleaning)
        critical_files = set()
        quality_files = set()

        if hasattr(self.context, "get"):
            # Dict-style context
            findings = self.context.get("findings", [])
            quick_wins = self.context.get("quick_wins", [])
        else:
            # Object-style context
            findings = getattr(self.context, "findings", [])
            quick_wins = getattr(self.context, "quick_wins", [])

        # Identify files with critical security issues
        for finding in findings:
            if isinstance(finding, dict):
                severity = finding.get("severity", "").lower()
                location = finding.get("location", "")
            else:
                severity = getattr(finding, "severity", "").lower()
                location = getattr(finding, "location", "")

            if severity in ["critical", "high"] and location:
                file_path = location.split(":")[0]
                critical_files.add(file_path)

        # Identify files with quality quick wins
        for qw in quick_wins:
            if isinstance(qw, dict):
                file_path = qw.get("file_path", "")
            else:
                file_path = getattr(qw, "file_path", "")

            if file_path:
                quality_files.add(file_path)

        if critical_files:
            logger.info(
                "cleaning_skipping_critical_files",
                skipped=len(critical_files),
                reason="security_issues_present",
            )

        if quality_files:
            logger.info(
                "cleaning_prioritizing_quality_files",
                prioritized=len(quality_files),
                reason="quality_quick_wins_available",
            )

        # Analyze each file for improvements
        for code_file in code_files:
            # Skip files with critical security issues (Tier 2: Context-Aware)
            if code_file.path in critical_files:
                logger.debug(
                    "cleaning_skipped_file",
                    file=code_file.path,
                    reason="critical_security_issue",
                )
                continue
            # Skip non-production files based on context
            file_context = self.context.get("file_contexts", {}).get(code_file.path)
            if file_context:
                if hasattr(file_context, "context"):
                    context_type = (
                        file_context.context.value
                        if hasattr(file_context.context, "value")
                        else str(file_context.context)
                    )
                elif isinstance(file_context, dict):
                    context_type = file_context.get("context", "PRODUCTION")
                else:
                    context_type = "PRODUCTION"

                if context_type in ["TEST", "EXAMPLE", "DOCUMENTATION"]:
                    continue

            # Run specialized cleaning orchestrator (Rule-Based, Fast)
            res = await orchestrator.analyze_async(code_file)
            all_suggestions.extend(res.suggestions)

            # Map suggestions to cleanings for Panel
            for sug in res.suggestions:
                all_cleanings.append(
                    Cleaning(
                        id=f"clean-{len(all_cleanings)}",
                        title=sug.issue.issue_type.value.replace("_", " ").title(),
                        detail=sug.suggestion,
                    )
                )

            # Heuristic: Skip LLM cleaning if code quality is already Good/Excellent (>7.0)
            skip_llm_for_quality = False
            phase_results = (
                self.context.get("phase_results", {})
                if isinstance(self.context, dict)
                else getattr(self.context, "phase_results", {})
            )
            file_metrics = phase_results.get("ANALYSIS", {}).get("metrics", {}).get(code_file.path)

            if file_metrics:
                overall_score = file_metrics.get("overall_score", 0.0)
                if overall_score >= 7.0:
                    skip_llm_for_quality = True
                    logger.info("skipping_llm_cleaning_high_quality", file=code_file.path, score=overall_score)

            if self.use_llm and not skip_llm_for_quality:
                files_for_llm.append(code_file)
            else:
                # If skipping LLM or LLM disabled, run rule-based fallback immediately/lazily?
                # Actually orchestrator.analyze_async does some rule based stuff, but _generate_rule_based_suggestions_async does MORE.
                # Let's collecting them for batch processing to avoid confusion, or run here?
                # Running here is fine as it's CPU bound and fast.
                if not self.use_llm and skip_llm_for_quality:
                    logger.debug("falling_back_to_rules_due_to_high_quality", file=code_file.path)

                sugs = await self._generate_rule_based_suggestions_async(code_file)
                rule_based_suggestions[code_file.path] = sugs

        # Batch Process LLM Suggestions
        llm_suggestions_map = {}
        if files_for_llm and self.llm_generator:
            logger.info("cleaning_batch_llm_start", count=len(files_for_llm))
            llm_suggestions_map = await self.llm_generator.generate_batch_suggestions_async(files_for_llm)

        # Merge Results
        for code_file in code_files:
            suggestions = llm_suggestions_map.get(code_file.path) or rule_based_suggestions.get(code_file.path) or {}

            for sug in suggestions.get("cleanings", []):
                all_cleanings.append(
                    Cleaning(
                        id=f"leg-clean-{len(all_cleanings)}",
                        title=sug.get("title", "Cleaning Suggestion"),
                        detail=sug.get("detail", ""),
                    )
                )

            for ref in suggestions.get("refactorings", []):
                all_refactorings.append(
                    Cleaning(
                        id=f"ref-{len(all_refactorings)}",
                        title=ref.get("title", "Refactoring"),
                        detail=ref.get("detail", ""),
                    )
                )

        # Calculate results
        quality_score_before = self.context.get("quality_score_before", 0.0)
        quality_score_after = self._calculate_improved_score(
            quality_score_before,
            all_cleanings,
            all_refactorings,
        )

        code_improvements = self._summarize_improvements(
            all_cleanings,
            all_refactorings,
        )

        result = CleaningPhaseResult(
            cleaning_suggestions=[c.to_json() if hasattr(c, "to_json") else c for c in all_cleanings],
            refactorings=[r.to_json() if hasattr(r, "to_json") else r for r in all_refactorings],
            quality_score_after=quality_score_after,
            code_improvements=code_improvements,
        )

        return result

    async def _generate_llm_suggestions_async(
        self,
        code_file: CodeFile,
    ) -> dict[str, Any]:
        """
        Generate cleaning suggestions using LLM.

        Args:
            code_file: Code file to analyze

        Returns:
            Dictionary with cleanings and refactorings
        """
        if not self.llm_generator:
            return await self._generate_rule_based_suggestions_async(code_file)

        try:
            # Delegate to LLM generator
            suggestions = await self.llm_generator.generate_suggestions_async(code_file)
            return suggestions

        except Exception as e:
            logger.error(
                "llm_suggestion_generation_failed",
                file=code_file.path,
                error=str(e),
            )
            # Fall back to rule-based suggestions
            return await self._generate_rule_based_suggestions_async(code_file)

    async def _generate_rule_based_suggestions_async(
        self,
        code_file: CodeFile,
    ) -> dict[str, Any]:
        """
        Generate cleaning suggestions using rules.

        Args:
            code_file: Code file to analyze

        Returns:
            Dictionary with cleanings and refactorings
        """
        cleanings = []
        refactorings = []

        # Analyze code patterns using pattern analyzer
        analysis = self.pattern_analyzer.analyze_code_patterns(code_file)

        # Generate suggestions based on patterns
        if analysis.get("duplicate_code"):
            cleanings.append(self.pattern_analyzer.create_duplication_suggestion(analysis["duplicate_code"]))

        if analysis.get("complex_functions"):
            refactorings.append(self.pattern_analyzer.create_complexity_suggestion(analysis["complex_functions"]))

        if analysis.get("naming_issues"):
            cleanings.append(self.pattern_analyzer.create_naming_suggestion(analysis["naming_issues"]))

        if analysis.get("dead_code"):
            cleanings.append(self.pattern_analyzer.create_dead_code_suggestion(analysis["dead_code"]))

        if analysis.get("import_issues"):
            cleanings.append(self.pattern_analyzer.create_import_suggestion(analysis["import_issues"]))

        return {
            "cleanings": cleanings,
            "refactorings": refactorings,
        }

    def _safe_get(self, obj: Any, key: str, default: Any = None) -> Any:
        """Safely get attribute from dict or object."""
        if isinstance(obj, dict):
            return obj.get(key, default)
        return getattr(obj, key, default)

    def _calculate_improved_score(
        self,
        quality_score_before: float,
        cleaning_suggestions: list,
        refactorings: list,
    ) -> float:
        """
        Calculate improved quality score (Potential Upside).

        Args:
            quality_score_before: Current quality score (can be 9.8 or 4.5 based on Findings/Baseline)
            cleaning_suggestions: List of cleaning suggestions
            refactorings: List of refactoring suggestions

        Returns:
            Estimated quality score after improvements, never exceeding 10.0
        """
        # Estimate improvement based on suggestions
        improvement = 0.0

        # Each cleaning suggestion adds small improvement
        for cleaning in cleaning_suggestions:
            impact = self._safe_get(cleaning, "impact", "medium")
            if impact == "high":
                improvement += 0.3
            elif impact == "medium":
                improvement += 0.2
            else:
                improvement += 0.1

        # Each refactoring adds larger improvement
        for refactoring in refactorings:
            impact = self._safe_get(refactoring, "impact", "high")
            if impact == "high":
                improvement += 0.5
            elif impact == "medium":
                improvement += 0.3
            else:
                improvement += 0.2

        # Realism cap: large batches of suggestions don't instantly jump the score.
        # Final min(…, 10.0) already prevents exceeding the ceiling.
        headroom = 10.0 - quality_score_before
        improvement = min(improvement, 5.0, headroom)

        quality_score_after = quality_score_before + improvement

        return round(quality_score_after, 1)

    def _summarize_improvements(
        self,
        cleaning_suggestions: list,
        refactorings: list,
    ) -> dict[str, Any]:
        """
        Summarize all improvements.

        Args:
            cleaning_suggestions: List of cleaning suggestions
            refactorings: List of refactoring suggestions

        Returns:
            Summary dictionary
        """
        # Count by type
        type_counts = {}
        for suggestion in cleaning_suggestions + refactorings:
            sug_type = self._safe_get(suggestion, "type", "other")
            type_counts[sug_type] = type_counts.get(sug_type, 0) + 1

        # Count by impact
        impact_counts = {"high": 0, "medium": 0, "low": 0}
        for suggestion in cleaning_suggestions + refactorings:
            impact = self._safe_get(suggestion, "impact", "medium")
            impact_counts[impact] = impact_counts.get(impact, 0) + 1

        # Count by effort
        effort_counts = {"high": 0, "medium": 0, "low": 0}
        for suggestion in cleaning_suggestions + refactorings:
            effort = self._safe_get(suggestion, "effort", "medium")
            effort_counts[effort] = effort_counts.get(effort, 0) + 1

        return {
            "total_suggestions": len(cleaning_suggestions) + len(refactorings),
            "cleanings": len(cleaning_suggestions),
            "refactorings": len(refactorings),
            "by_type": type_counts,
            "by_impact": impact_counts,
            "by_effort": effort_counts,
            "quick_wins": [
                s
                for s in cleaning_suggestions + refactorings
                if self._safe_get(s, "impact") in ["high", "medium"] and self._safe_get(s, "effort") == "low"
            ][:5],  # Top 5 quick wins
        }

    def _format_findings(
        self,
        findings: list[dict[str, Any]],
    ) -> str:
        """Format findings for prompt."""
        if not findings:
            return "No security issues in this file"

        formatted = []
        for finding in findings:
            formatted.append(
                f"- {finding.get('type', 'issue')}: "
                f"{finding.get('message', 'Security issue')} "
                f"(line {finding.get('line_number', 'unknown')})"
            )

        return "\n".join(formatted)
