"""Human-readable text formatter with beginner-friendly output."""

from __future__ import annotations

from typing import List, TextIO
import sys

from stablestack.models import Finding, Severity


class TextFormatter:
    """Formats findings as human-readable, beginner-friendly text."""

    # ANSI color codes
    COLORS = {
        "reset": "\033[0m",
        "bold": "\033[1m",
        "dim": "\033[2m",
        "red": "\033[31m",
        "yellow": "\033[33m",
        "blue": "\033[34m",
        "cyan": "\033[36m",
        "gray": "\033[90m",
    }

    def __init__(self, use_color: bool = True, show_why: bool = True, top_n: int = 0):
        """
        Initialize the formatter.

        Args:
            use_color: Whether to use ANSI colors in output
            show_why: Whether to show the "why this matters" explanation
            top_n: Limit output to top N findings (0 = show all)
        """
        self.use_color = use_color and sys.stdout.isatty()
        self.show_why = show_why
        self.top_n = top_n

    def format(self, findings: List[Finding], output: TextIO = sys.stdout) -> None:
        """
        Format and write findings to output.

        Args:
            findings: List of findings to format
            output: Output stream (default: stdout)
        """
        if not findings:
            self._write(output, self._color("gray", "No issues found.\n"))
            return

        # Group findings by file
        by_file: dict[str, List[Finding]] = {}
        for finding in findings:
            by_file.setdefault(finding.file, []).append(finding)

        # Count by severity
        error_count = sum(1 for f in findings if f.severity == Severity.ERROR)
        warning_count = sum(1 for f in findings if f.severity == Severity.WARNING)
        info_count = sum(1 for f in findings if f.severity == Severity.INFO)

        summary_line = self._summary_line(error_count, warning_count, info_count, len(by_file))

        # Print summary at the top so it's visible even when output is truncated
        self._write(output, summary_line + "\n")
        self._write(output, "─" * 60 + "\n")

        # Sort findings: errors first, then warnings, then info
        severity_order = {Severity.ERROR: 0, Severity.WARNING: 1, Severity.INFO: 2}
        sorted_findings = sorted(findings, key=lambda f: (severity_order[f.severity], f.file, f.line))

        # Apply top-N limit if set
        truncated = 0
        display_findings = sorted_findings
        if self.top_n and self.top_n < len(sorted_findings):
            display_findings = sorted_findings[:self.top_n]
            truncated = len(sorted_findings) - self.top_n

        # Re-group the (possibly limited) findings by file
        display_by_file: dict[str, List[Finding]] = {}
        for finding in display_findings:
            display_by_file.setdefault(finding.file, []).append(finding)

        # Output each file's findings
        for file_path, file_findings in sorted(display_by_file.items()):
            self._write(output, f"\n{self._color('bold', file_path)}\n")

            for finding in sorted(file_findings, key=lambda f: f.line):
                self._format_finding(finding, output)

        if truncated:
            self._write(output, f"\n{self._color('dim', f'... and {truncated} more (use --top N to adjust)')}\n")

        # Repeat summary at the bottom
        self._write(output, "\n" + "─" * 60 + "\n")
        self._write(output, summary_line + "\n")

    def _summary_line(self, errors: int, warnings: int, infos: int, file_count: int) -> str:
        """Build the summary line."""
        parts = []
        if errors:
            parts.append(self._color("red", f"{errors} error{'s' if errors != 1 else ''}"))
        if warnings:
            parts.append(self._color("yellow", f"{warnings} warning{'s' if warnings != 1 else ''}"))
        if infos:
            parts.append(self._color("blue", f"{infos} info"))
        return f"Found {', '.join(parts)} in {file_count} file{'s' if file_count != 1 else ''}"

    def _format_finding(self, finding: Finding, output: TextIO) -> None:
        """Format a single finding."""
        # Severity indicator
        severity_colors = {
            Severity.ERROR: "red",
            Severity.WARNING: "yellow",
            Severity.INFO: "blue",
        }
        severity_symbols = {
            Severity.ERROR: "✖",
            Severity.WARNING: "⚠",
            Severity.INFO: "ℹ",
        }
        color = severity_colors[finding.severity]
        symbol = severity_symbols[finding.severity]

        # Header: file:line and message (file prefix makes grep output self-contained)
        header = f"  {self._color(color, symbol)} {self._color('dim', finding.file)}:{finding.line}: {finding.message}"
        self._write(output, header + "\n")

        # Show the problematic code
        self._write(output, f"\n    {self._color('dim', 'Problem:')}\n")
        self._write(output, f"      {self._color('cyan', finding.code)}\n")

        # Show the fix hint
        self._write(output, f"\n    {self._color('dim', 'Fix:')}\n")
        for line in finding.fix_hint.split('\n'):
            self._write(output, f"      {line}\n")

        # Show why this matters (for beginners)
        if self.show_why and finding.why:
            self._write(output, f"\n    {self._color('dim', 'Why?')} {finding.why}\n")

        self._write(output, "\n")

    def _color(self, color: str, text: str) -> str:
        """Apply ANSI color if colors are enabled."""
        if not self.use_color:
            return text
        return f"{self.COLORS.get(color, '')}{text}{self.COLORS['reset']}"

    def _write(self, output: TextIO, text: str) -> None:
        """Write text to output stream."""
        output.write(text)
