"""Rich-formatted terminal output for security audit results.

Produces beautifully structured, color-coded output including:
- Summary panel with severity distribution bar chart
- Findings table with severity-colored rows
- Recommendation panels with code fix suggestions
- Progress indicator during scan
"""

from __future__ import annotations

from typing import Final

from rich.box import HEAVY, ROUNDED
from rich.console import Console
from rich.panel import Panel
from rich.syntax import Syntax
from rich.table import Table
from rich.text import Text
from rich.tree import Tree

from aicippy.security.models import (
    AuditFinding,
    AuditSeverity,
    SecurityAuditResult,
)

# ============================================================================
# Constants
# ============================================================================

# Severity display configuration: (label, color, bold)
_SEVERITY_STYLES: Final[dict[AuditSeverity, tuple[str, bool]]] = {
    AuditSeverity.CRITICAL: ("red", True),
    AuditSeverity.HIGH: ("red", False),
    AuditSeverity.MEDIUM: ("yellow", False),
    AuditSeverity.LOW: ("blue", False),
    AuditSeverity.INFO: ("dim", False),
}

_BAR_TOTAL_WIDTH: Final[int] = 16


class SecurityAuditFormatter:
    """Formats security audit results for Rich terminal display.

    Args:
        console: Rich Console instance for output.

    """

    def __init__(self, console: Console | None = None) -> None:
        self._console = console or Console()

    # ── Public API ─────────────────────────────────────────────────────

    def render_full_report(self, result: SecurityAuditResult) -> None:
        """Render the complete audit report to the console.

        Displays summary panel, findings table, and recommendations.

        Args:
            result: The complete SecurityAuditResult to render.

        """
        self._console.print()
        self._console.print(self.build_summary_panel(result))
        self._console.print()

        if result.findings:
            self._console.print(self.build_findings_table(result.findings))
            self._console.print()

        if result.recommendations:
            self._console.print(self.build_recommendations_panel(result.recommendations))
            self._console.print()

        # Show detailed findings with fix code for critical/high items
        critical_high = [f for f in result.findings if f.severity in (AuditSeverity.CRITICAL, AuditSeverity.HIGH)]
        if critical_high:
            self._console.print(self.build_findings_detail_tree(critical_high))
            self._console.print()

    def render_streaming_finding(self, finding: AuditFinding) -> None:
        """Render a single finding as it streams in.

        Args:
            finding: The AuditFinding to display inline.

        """
        color, bold = _SEVERITY_STYLES.get(finding.severity, ("white", False))
        style = f"bold {color}" if bold else color

        line = Text()
        line.append("  ", style="dim")
        severity_label = finding.severity.value.upper()
        line.append(f"[{severity_label:^8s}]", style=style)
        line.append("  ", style="dim")
        line.append(finding.category, style="cyan")
        line.append("  ", style="dim")
        line.append(finding.title, style="white")
        if finding.affected_file:
            line.append(f"  ({finding.affected_file}", style="dim")
            if finding.affected_line is not None:
                line.append(f":{finding.affected_line}", style="dim")
            line.append(")", style="dim")

        self._console.print(line)

    # ── Panel Builders ─────────────────────────────────────────────────

    def build_summary_panel(self, result: SecurityAuditResult) -> Panel:
        """Build the summary panel with severity distribution chart.

        Args:
            result: SecurityAuditResult containing summary data.

        Returns:
            Rich Panel with formatted summary.

        """
        content = Text()

        # Scan info line
        summary = result.summary
        if summary:
            content.append(
                f"Scan: {result.scan_scope} | "
                f"Files: {summary.files_scanned:,} | "
                f"Lines: {summary.lines_scanned:,} | "
                f"Duration: {summary.scan_duration_seconds:.1f}s",
                style="white",
            )
            content.append("\n\n")

            # Findings total
            content.append(f"Findings: {summary.total_findings} total\n", style="bold white")

            # Severity distribution chart
            severity_counts: list[tuple[str, int, AuditSeverity]] = [
                ("CRITICAL", summary.critical_count, AuditSeverity.CRITICAL),
                ("HIGH", summary.high_count, AuditSeverity.HIGH),
                ("MEDIUM", summary.medium_count, AuditSeverity.MEDIUM),
                ("LOW", summary.low_count, AuditSeverity.LOW),
                ("INFO", summary.info_count, AuditSeverity.INFO),
            ]

            for label, count, severity in severity_counts:
                color, bold = _SEVERITY_STYLES[severity]
                bar_style = f"bold {color}" if bold else color

                # Calculate bar width
                filled = int(count / summary.total_findings * _BAR_TOTAL_WIDTH) if summary.total_findings > 0 else 0
                empty = _BAR_TOTAL_WIDTH - filled
                bar = "\u2588" * filled + "\u2591" * empty
                pct = int(count / summary.total_findings * 100) if summary.total_findings > 0 else 0

                content.append("  ", style="dim")
                content.append("\u25a0 ", style=bar_style)
                content.append(f"{label:<8s}", style=bar_style)
                content.append(f" {count:<4d} ", style="white")
                content.append(bar, style=bar_style)
                content.append(f"  {pct}%\n", style="dim")
        else:
            # No summary yet (still in progress or error)
            status_display = result.status.value.upper()
            content.append(f"Status: {status_display}\n", style="bold yellow")
            if result.findings:
                content.append(
                    f"Findings so far: {len(result.findings)}\n",
                    style="white",
                )

        # AI confidence
        content.append("\n")
        confidence_pct = result.ai_confidence * 100
        confidence_color = "green" if confidence_pct >= 80 else "yellow" if confidence_pct >= 50 else "red"
        content.append(f"AI Confidence: {confidence_pct:.1f}%", style=f"bold {confidence_color}")

        return Panel(
            content,
            title="[bold]Security Audit Results[/bold]",
            title_align="center",
            border_style="#667eea",
            box=ROUNDED,
            padding=(1, 2),
        )

    def build_findings_table(self, findings: list[AuditFinding]) -> Table:
        """Build a Rich Table displaying all findings.

        Args:
            findings: List of AuditFinding instances.

        Returns:
            Rich Table with severity-colored rows.

        """
        table = Table(
            title="[bold]Detailed Findings[/bold]",
            border_style="#667eea",
            title_style="bold #3b82f6",
            header_style="bold #f093fb",
            box=HEAVY,
            show_lines=True,
        )
        table.add_column("Severity", style="bold", width=10, justify="center")
        table.add_column("Category", style="cyan", width=16)
        table.add_column("Title", style="white", min_width=20)
        table.add_column("File", style="dim", width=20)
        table.add_column("Line", style="dim", width=6, justify="right")

        # Sort by severity: critical first
        severity_order = {
            AuditSeverity.CRITICAL: 0,
            AuditSeverity.HIGH: 1,
            AuditSeverity.MEDIUM: 2,
            AuditSeverity.LOW: 3,
            AuditSeverity.INFO: 4,
        }
        sorted_findings = sorted(
            findings,
            key=lambda f: severity_order.get(f.severity, 5),
        )

        for finding in sorted_findings:
            color, bold = _SEVERITY_STYLES.get(finding.severity, ("white", False))
            sev_style = f"bold {color}" if bold else color

            line_str = str(finding.affected_line) if finding.affected_line is not None else "-"

            # Truncate file path for display
            file_display = finding.affected_file
            if len(file_display) > 20:
                file_display = "..." + file_display[-17:]

            table.add_row(
                Text(finding.severity.value.upper(), style=sev_style),
                finding.category,
                finding.title,
                file_display,
                line_str,
            )

        return table

    def build_recommendations_panel(self, recommendations: list[str]) -> Panel:
        """Build a panel showing high-level recommendations.

        Args:
            recommendations: List of recommendation strings.

        Returns:
            Rich Panel with numbered recommendations.

        """
        content = Text()
        for i, rec in enumerate(recommendations, start=1):
            content.append(f"  {i}. ", style="bold #f59e0b")
            content.append(f"{rec}\n", style="white")

        return Panel(
            content,
            title="[bold]Recommendations[/bold]",
            title_align="left",
            border_style="#f59e0b",
            box=ROUNDED,
            padding=(1, 2),
        )

    def build_findings_detail_tree(self, findings: list[AuditFinding]) -> Panel:
        """Build a detailed tree view of critical/high findings with fix code.

        Args:
            findings: List of critical/high findings to detail.

        Returns:
            Rich Panel containing a Tree of findings with descriptions and fixes.

        """
        tree = Tree(
            "[bold red]Critical & High Severity Details[/bold red]",
            guide_style="red",
        )

        for finding in findings:
            color, bold = _SEVERITY_STYLES.get(finding.severity, ("white", False))
            sev_style = f"bold {color}" if bold else color

            # Finding node
            label = Text()
            label.append(f"[{finding.severity.value.upper()}] ", style=sev_style)
            label.append(finding.title, style="bold white")
            node = tree.add(label)

            # Description
            node.add(Text(finding.description, style="dim"))

            # File and line
            location = Text()
            location.append("File: ", style="dim")
            location.append(finding.affected_file, style="cyan")
            if finding.affected_line is not None:
                location.append(f":{finding.affected_line}", style="cyan")
            node.add(location)

            # CWE / OWASP
            if finding.cwe_id or finding.owasp_category:
                meta = Text()
                if finding.cwe_id:
                    meta.append(f"CWE: {finding.cwe_id}", style="yellow")
                if finding.cwe_id and finding.owasp_category:
                    meta.append(" | ", style="dim")
                if finding.owasp_category:
                    meta.append(f"OWASP: {finding.owasp_category}", style="yellow")
                node.add(meta)

            # Recommendation
            rec_text = Text()
            rec_text.append("Fix: ", style="bold green")
            rec_text.append(finding.recommendation, style="white")
            node.add(rec_text)

            # Code fix
            if finding.fix_code:
                code_panel = Panel(
                    Syntax(
                        finding.fix_code,
                        "python",
                        theme="monokai",
                        line_numbers=False,
                    ),
                    title="[dim]Suggested Fix[/dim]",
                    border_style="green",
                    padding=(0, 1),
                )
                node.add(code_panel)

            # References
            if finding.references:
                refs_node = node.add(Text("References:", style="dim"))
                for ref in finding.references:
                    refs_node.add(Text(f"  {ref}", style="dim underline"))

        return Panel(
            tree,
            border_style="red",
            box=ROUNDED,
            padding=(1, 2),
        )

    def build_progress_panel(
        self,
        status: str,
        findings_count: int,
        elapsed_seconds: float,
    ) -> Panel:
        """Build a progress panel for display during streaming.

        Args:
            status: Current scan status text.
            findings_count: Number of findings discovered so far.
            elapsed_seconds: Elapsed time in seconds.

        Returns:
            Rich Panel showing scan progress.

        """
        content = Text()
        content.append("\u25d0 ", style="bold #667eea")
        content.append(status, style="bold white")
        content.append("  |  ", style="dim")
        content.append(f"{findings_count}", style="bold #f59e0b")
        content.append(" findings", style="dim")
        content.append("  |  ", style="dim")
        content.append(f"{elapsed_seconds:.1f}s", style="bold #3b82f6")
        content.append(" elapsed", style="dim")

        return Panel(
            content,
            border_style="#667eea",
            box=ROUNDED,
            padding=(0, 2),
        )
