"""PR review agent for generating narrative reviews."""

from __future__ import annotations

import json
import logging
from dataclasses import dataclass
from typing import Any, Optional

from emdash.agent.toolkit import AgentToolkit
from emdash.agent.providers import get_provider
from emdash.agent.providers.factory import DEFAULT_MODEL
from emdash.templates import load_template_for_agent
from emdash.agent.enhanced_review import EnhancedReviewAgent, EnhancedReviewResult


log = logging.getLogger(__name__)


@dataclass
class DiffFile:
    """Parsed diff file data."""

    path: str
    hunks: list[str]


class ReviewAgent:
    """Agent that generates PR review reports with enhanced technical analysis."""

    def __init__(
        self,
        model: str = DEFAULT_MODEL,
        max_files: int = 50,
        max_tokens: int = 50000,
        verbose: bool = True,
        enhanced_mode: bool = True,
    ):
        self.provider = get_provider(model)
        self.toolkit = AgentToolkit(enable_session=False)
        self.model = model
        self.max_files = max_files
        self.max_tokens = max_tokens
        self.verbose = verbose
        self.enhanced_mode = enhanced_mode
        
        # Initialize enhanced review agent for technical analysis
        if self.enhanced_mode:
            self.enhanced_agent = EnhancedReviewAgent(
                model=model,
                max_files=max_files,
                max_tokens=max_tokens,
                verbose=verbose
            )

    def search_prs(
        self,
        query: str,
        state: str = "all",
        per_page: int = 100,
        max_detail_lookups: int = 20,
    ) -> list[dict]:
        """Search PRs by title/number and PR body."""
        result = self.toolkit.execute(
            "github_list_prs",
            state=state,
            per_page=per_page,
        )
        if not result.success:
            raise ValueError(result.error or "Failed to list PRs")

        prs = result.data.get("prs", [])
        query_lower = query.lower()
        filtered = []
        for pr in prs:
            title = pr.get("title", "")
            number = pr.get("number")
            if query_lower in title.lower() or query_lower in str(number):
                filtered.append(pr)

        if filtered:
            return filtered

        lookups = 0
        for pr in prs:
            number = pr.get("number")
            if not number:
                continue
            if lookups >= max_detail_lookups:
                break
            details = self.toolkit.execute(
                "github_pr_details",
                pull_number=number,
                include_diff=False,
                include_comments=False,
                include_reviews=False,
                include_review_comments=False,
            )
            lookups += 1
            if not details.success:
                continue
            pr_data = details.data.get("pr", {})
            body = pr_data.get("body", "") if isinstance(pr_data, dict) else ""
            if body and query_lower in body.lower():
                filtered.append(pr)

        if filtered:
            return filtered

        search_result = self.toolkit.execute(
            "github_search_prs",
            query=query,
            per_page=per_page,
        )
        if search_result.success:
            return search_result.data.get("prs", [])

        return filtered

    def generate_review(self, pr_number: int) -> str:
        """Generate a PR review for the given PR number."""
        if self.enhanced_mode:
            return self._generate_enhanced_review(pr_number)
        else:
            return self._generate_basic_review(pr_number)

    def _generate_enhanced_review(self, pr_number: int) -> str:
        """Generate an enhanced PR review with technical analysis."""
        if self.verbose:
            log.info(f"Generating enhanced review for PR #{pr_number}")
        
        try:
            # Use the enhanced review agent
            enhanced_result = self.enhanced_agent.generate_enhanced_review(pr_number)
            
            # Format the enhanced result into a comprehensive review
            return self._format_enhanced_review(enhanced_result)
            
        except Exception as e:
            log.error(f"Enhanced review failed, falling back to basic: {e}")
            return self._generate_basic_review(pr_number)

    def _generate_basic_review(self, pr_number: int) -> str:
        """Generate basic PR review (original implementation)."""
        pr_result = self.toolkit.execute(
            "github_pr_details",
            pull_number=pr_number,
            include_diff=True,
            include_comments=True,
            include_reviews=True,
            include_review_comments=True,
        )
        if not pr_result.success:
            raise ValueError(pr_result.error or "Failed to fetch PR details")

        data = pr_result.data
        pr = data.get("pr", {})
        diff_text = data.get("diff", "") if isinstance(data.get("diff"), str) else ""
        comments = data.get("comments", [])
        reviews = data.get("reviews", [])
        review_comments = data.get("review_comments", [])

        files, total_files = self._parse_diff(diff_text)

        similar_prs = []
        title = pr.get("title") or ""
        if title:
            similar = self.toolkit.execute(
                "find_similar_prs",
                description=title,
                state="merged",
                limit=5,
            )
            if similar.success:
                similar_prs = similar.data.get("prs", [])

        context = {
            "pr": {
                "number": pr_number,
                "title": pr.get("title"),
                "author": pr.get("user", {}).get("login") if isinstance(pr.get("user"), dict) else None,
                "state": pr.get("state"),
                "merged": pr.get("merged"),
                "created_at": pr.get("created_at"),
                "updated_at": pr.get("updated_at"),
                "html_url": pr.get("html_url"),
                "changed_files": pr.get("changed_files"),
                "additions": pr.get("additions"),
                "deletions": pr.get("deletions"),
            },
            "review_counts": {
                "comments": len(comments) if isinstance(comments, list) else 0,
                "reviews": len(reviews) if isinstance(reviews, list) else 0,
                "review_comments": len(review_comments) if isinstance(review_comments, list) else 0,
            },
            "diff": {
                "files": [
                    {"path": f.path, "hunks": f.hunks}
                    for f in files
                ],
                "total_files": total_files,
                "included_files": len(files),
                "truncated": total_files > len(files),
            },
            "comments": comments,
            "reviews": reviews,
            "review_comments": review_comments,
            "similar_prs": similar_prs,
        }

        template = load_template_for_agent("pr-review")
        user_content = self._build_user_prompt(context)

        response = self.provider.chat(
            [
                {"role": "system", "content": template},
                {"role": "user", "content": user_content},
            ]
        )

        report = response.content or ""
        full_diff = self._format_full_diff(files, total_files)
        comments_block = self._format_comments(comments, reviews, review_comments)
        return "\n\n".join([report, comments_block, full_diff]).strip()

    def _format_enhanced_review(self, enhanced_result: EnhancedReviewResult) -> str:
        """Format enhanced review result into comprehensive markdown report."""
        sections = []
        
        # Executive Summary
        sections.append(self._format_executive_summary(enhanced_result))
        
        # Technical Risk Matrix
        if enhanced_result.technical_risks:
            sections.append(self._format_risk_matrix(enhanced_result.technical_risks))
        
        # Code Quality Analysis
        sections.append(self._format_quality_analysis(enhanced_result.quality_metrics))
        
        # Actionable Recommendations
        if enhanced_result.recommendations:
            sections.append(self._format_recommendations(enhanced_result.recommendations))
        
        # Technical Context
        if enhanced_result.code_context:
            sections.append(self._format_code_context(enhanced_result.code_context))
        
        # Combine all sections
        return "\n\n".join(sections)

    def _format_executive_summary(self, result: EnhancedReviewResult) -> str:
        """Format executive summary section."""
        high_risks = [r for r in result.technical_risks if r.level == 'HIGH']
        
        summary_parts = [
            "## A) Executive Summary",
            f"**Decision**: {result.verdict}",
            f"**Confidence**: {result.confidence_score:.0%}",
            f"**Risk Level**: {'HIGH' if high_risks else 'MEDIUM' if result.technical_risks else 'LOW'}",
        ]
        
        if high_risks:
            summary_parts.append("\n**Critical Issues** (Blockers):")
            for i, risk in enumerate(high_risks[:3], 1):
                summary_parts.append(f"{i}. **[{risk.category}]** {risk.description}")
        
        return "\n".join(summary_parts)

    def _format_risk_matrix(self, risks: list) -> str:
        """Format technical risk matrix."""
        matrix_parts = ["\n## B) Technical Risk Matrix"]
        matrix_parts.append("| Risk Category | Level | Description | Evidence | Mitigation |")
        matrix_parts.append("|---------------|--------|-------------|----------|------------|")
        
        for risk in risks:
            level_indicator = "🚨" if risk.level == 'HIGH' else "⚠️" if risk.level == 'MEDIUM' else "ℹ️"
            matrix_parts.append(
                f"| {risk.category} | {level_indicator} {risk.level} | {risk.description[:50]}... | "
                f"{risk.evidence[:30]}... | {risk.mitigation[:40]}... |"
            )
        
        return "\n".join(matrix_parts)

    def _format_quality_analysis(self, metrics: 'CodeQualityMetrics') -> str:
        """Format code quality analysis section."""
        analysis_parts = ["\n## C) Code Quality Analysis"]
        
        analysis_parts.append("**Complexity Metrics:**")
        analysis_parts.append(f"- Average Cyclomatic Complexity: {metrics.complexity_score:.1f}/10")
        analysis_parts.append(f"- Security Issues Found: {len(metrics.security_issues)}")
        analysis_parts.append(f"- Performance Anti-patterns: {len(metrics.performance_patterns)}")
        analysis_parts.append(f"- Documentation Coverage: {metrics.documentation_completeness:.1f}%")
        analysis_parts.append(f"- Test Coverage Impact: {metrics.test_coverage_impact}")
        analysis_parts.append(f"- Maintainability Score: {metrics.maintainability_score:.1f}/10")
        
        return "\n".join(analysis_parts)

    def _format_recommendations(self, recommendations: list) -> str:
        """Format actionable recommendations section."""
        rec_parts = ["\n## D) Actionable Recommendations"]
        
        # Group by priority
        blockers = [r for r in recommendations if "BLOCKER" in r]
        high_priority = [r for r in recommendations if "HIGH PRIORITY" in r]
        improvements = [r for r in recommendations if "IMPROVEMENTS" in r]
        
        if blockers:
            rec_parts.append("\n#### 🚨 **BLOCKERS** (Must fix before merge)")
            rec_parts.extend(blockers)
        
        if high_priority:
            rec_parts.append("\n#### ⚠️ **HIGH PRIORITY** (Should fix before merge)")
            rec_parts.extend(high_priority)
        
        if improvements:
            rec_parts.append("\n#### 💡 **IMPROVEMENTS** (Nice to have)")
            rec_parts.extend(improvements)
        
        return "\n".join(rec_parts)

    def _format_code_context(self, context: dict) -> str:
        """Format code context section."""
        context_parts = ["\n## E) Code Context Analysis"]
        
        if context.get('similar_patterns'):
            context_parts.append(f"**Similar Patterns Found**: {len(context['similar_patterns'])} code patterns")
        
        if context.get('dependencies'):
            context_parts.append(f"**Dependencies Analyzed**: {len(context['dependencies'])} files")
        
        if context.get('security_issues'):
            context_parts.append(f"**Security Context**: {len(context['security_issues'])} files with security analysis")
        
        if context.get('performance_issues'):
            context_parts.append(f"**Performance Context**: {len(context['performance_issues'])} files with performance analysis")
        
        return "\n".join(context_parts) if len(context_parts) > 1 else ""

    def _parse_diff(self, diff_text: str) -> tuple[list[DiffFile], int]:
        """Parse unified diff into file hunks."""
        if not diff_text:
            return [], 0

        files: list[DiffFile] = []
        current_path = None
        current_hunks: list[str] = []
        total_files = 0

        for line in diff_text.splitlines():
            if line.startswith("diff --git "):
                if current_path:
                    files.append(DiffFile(path=current_path, hunks=current_hunks))
                total_files += 1
                current_path = None
                current_hunks = []
                continue

            if line.startswith("+++ b/"):
                current_path = line.replace("+++ b/", "").strip()
                continue

            if current_path:
                current_hunks.append(line)

        if current_path:
            files.append(DiffFile(path=current_path, hunks=current_hunks))

        return files[: self.max_files], total_files

    def _estimate_tokens(self, text: str) -> int:
        """Estimate token count (rough approximation)."""
        return len(text) // 4

    def _build_user_prompt(self, context: dict[str, Any]) -> str:
        """Build a prompt for the LLM, enforcing token limits."""
        self._trim_diff_hunks(context)
        sections = []

        sections.append("PR Metadata:\n" + json.dumps(context["pr"], indent=2))
        sections.append("Review Counts:\n" + json.dumps(context["review_counts"], indent=2))

        diff_section = json.dumps(context["diff"], indent=2)
        sections.append("Diff Summary:\n" + diff_section)

        sections.append("Reviews:\n" + json.dumps(context["reviews"], indent=2))
        sections.append("Review Comments:\n" + json.dumps(context["review_comments"], indent=2))
        sections.append("Comments:\n" + json.dumps(context["comments"], indent=2))
        sections.append("Similar PRs:\n" + json.dumps(context["similar_prs"], indent=2))

        prompt = "\n\n".join(sections)

        if self._estimate_tokens(prompt) <= self.max_tokens:
            return prompt

        # Trim heavy sections in order: review comments, comments, reviews.
        slim_context = dict(context)
        slim_context["review_comments"] = []
        slim_context["comments"] = []
        slim_context["reviews"] = []

        sections = [
            "PR Metadata:\n" + json.dumps(slim_context["pr"], indent=2),
            "Review Counts:\n" + json.dumps(slim_context["review_counts"], indent=2),
            "Diff Summary:\n" + json.dumps(slim_context["diff"], indent=2),
            "Similar PRs:\n" + json.dumps(slim_context["similar_prs"], indent=2),
        ]

        prompt = "\n\n".join(sections)
        if self._estimate_tokens(prompt) <= self.max_tokens:
            return prompt

        # Final fallback: hard truncate
        max_chars = self.max_tokens * 4
        return prompt[:max_chars]

    def _trim_diff_hunks(self, context: dict[str, Any]) -> None:
        """Trim diff hunks to fit a portion of the token budget."""
        diff = context.get("diff", {})
        files = diff.get("files", [])
        if not isinstance(files, list):
            return

        budget_chars = int(self.max_tokens * 4 * 0.6)
        remaining = budget_chars
        for file_info in files:
            hunks = file_info.get("hunks", [])
            if not isinstance(hunks, list):
                continue
            joined = "\n".join(hunks)
            if remaining <= 0:
                file_info["hunks"] = []
                continue
            if len(joined) > remaining:
                file_info["hunks"] = [joined[:remaining]]
                remaining = 0
            else:
                remaining -= len(joined)

    def _format_full_diff(self, files: list[DiffFile], total_files: int) -> str:
        """Render full diff section in markdown."""
        lines = [
            "## Full Diff",
            "",
            f"Included files: {len(files)} / {total_files}",
        ]
        if total_files > len(files):
            lines.append("Note: diff limited by max_files setting.")
        for diff_file in files:
            lines.append("")
            lines.append(f"### {diff_file.path}")
            lines.append("```diff")
            lines.extend(diff_file.hunks)
            lines.append("```")
        return "\n".join(lines).strip()

    def _format_comments(
        self,
        comments: list[Any],
        reviews: list[Any],
        review_comments: list[Any],
    ) -> str:
        """Render comments and reviews in markdown."""
        lines = ["## Reviews & Comments", ""]

        if reviews:
            lines.append("### Reviews")
            for item in reviews:
                if not isinstance(item, dict):
                    continue
                user = item.get("user", {}).get("login") if isinstance(item.get("user"), dict) else None
                state = item.get("state")
                body = item.get("body", "")
                lines.append(f"- {user or 'unknown'}: {state or 'unknown'}")
                if body:
                    lines.append(f"  - {body}")
            lines.append("")

        if review_comments:
            lines.append("### Inline Review Comments")
            for item in review_comments:
                if not isinstance(item, dict):
                    continue
                user = item.get("user", {}).get("login") if isinstance(item.get("user"), dict) else None
                path = item.get("path") or "unknown"
                position = item.get("position")
                body = item.get("body", "")
                location = f"{path}:{position}" if position is not None else path
                lines.append(f"- {user or 'unknown'} on `{location}`")
                if body:
                    lines.append(f"  - {body}")
            lines.append("")

        if comments:
            lines.append("### PR Comments")
            for item in comments:
                if not isinstance(item, dict):
                    continue
                user = item.get("user", {}).get("login") if isinstance(item.get("user"), dict) else None
                body = item.get("body", "")
                lines.append(f"- {user or 'unknown'}")
                if body:
                    lines.append(f"  - {body}")
            lines.append("")

        if not reviews and not review_comments and not comments:
            lines.append("No review or comment data available.")

        return "\n".join(lines).strip()
