"""
HTML Acceptance Test Report Generator

Generates HTML reports showing frame buffers from acceptance tests.
Uses NullDisplay to capture frames and renders them with monospace font.
"""

import html
from datetime import datetime
from pathlib import Path
from typing import Any

ANSI_256_TO_RGB = {
    0: (0, 0, 0),
    1: (128, 0, 0),
    2: (0, 128, 0),
    3: (128, 128, 0),
    4: (0, 0, 128),
    5: (128, 0, 128),
    6: (0, 128, 128),
    7: (192, 192, 192),
    8: (128, 128, 128),
    9: (255, 0, 0),
    10: (0, 255, 0),
    11: (255, 255, 0),
    12: (0, 0, 255),
    13: (255, 0, 255),
    14: (0, 255, 255),
    15: (255, 255, 255),
}


def ansi_to_rgb(color_code: int) -> tuple[int, int, int]:
    """Convert ANSI 256-color code to RGB tuple."""
    if 0 <= color_code <= 15:
        return ANSI_256_TO_RGB.get(color_code, (255, 255, 255))
    elif 16 <= color_code <= 231:
        color_code -= 16
        r = (color_code // 36) * 51
        g = ((color_code % 36) // 6) * 51
        b = (color_code % 6) * 51
        return (r, g, b)
    elif 232 <= color_code <= 255:
        gray = (color_code - 232) * 10 + 8
        return (gray, gray, gray)
    return (255, 255, 255)


def parse_ansi_line(line: str) -> list[dict[str, Any]]:
    """Parse a single line with ANSI escape codes into styled segments.

    Returns list of dicts with 'text', 'fg', 'bg', 'bold' keys.
    """
    import re

    segments = []
    current_fg = None
    current_bg = None
    current_bold = False
    pos = 0

    # Find all ANSI escape sequences
    escape_pattern = re.compile(r"\x1b\[([0-9;]*)m")

    while pos < len(line):
        match = escape_pattern.search(line, pos)
        if not match:
            # Remaining text with current styling
            if pos < len(line):
                text = line[pos:]
                if text:
                    segments.append(
                        {
                            "text": text,
                            "fg": current_fg,
                            "bg": current_bg,
                            "bold": current_bold,
                        }
                    )
            break

        # Add text before escape sequence
        if match.start() > pos:
            text = line[pos : match.start()]
            if text:
                segments.append(
                    {
                        "text": text,
                        "fg": current_fg,
                        "bg": current_bg,
                        "bold": current_bold,
                    }
                )

        # Parse escape sequence
        codes = match.group(1).split(";") if match.group(1) else ["0"]
        for code in codes:
            code = code.strip()
            if not code or code == "0":
                current_fg = None
                current_bg = None
                current_bold = False
            elif code == "1":
                current_bold = True
            elif code.isdigit():
                code_int = int(code)
                if 30 <= code_int <= 37:
                    current_fg = ansi_to_rgb(code_int - 30 + 8)
                elif 90 <= code_int <= 97:
                    current_fg = ansi_to_rgb(code_int - 90)
                elif code_int == 38:
                    current_fg = (255, 255, 255)
                elif code_int == 39:
                    current_fg = None

        pos = match.end()

    return segments


def render_line_to_html(line: str) -> str:
    """Render a single terminal line to HTML with styling."""
    import re

    result = ""
    pos = 0
    current_fg = None
    current_bg = None
    current_bold = False

    escape_pattern = re.compile(r"(\x1b\[[0-9;]*m)|(\x1b\[([0-9]+);([0-9]+)H)")

    while pos < len(line):
        match = escape_pattern.search(line, pos)
        if not match:
            # Remaining text
            if pos < len(line):
                text = html.escape(line[pos:])
                if text:
                    style = _build_style(current_fg, current_bg, current_bold)
                    result += f"<span{style}>{text}</span>"
            break

        # Handle cursor positioning - just skip it for rendering
        if match.group(2):  # Cursor positioning \x1b[row;colH
            pos = match.end()
            continue

        # Handle style codes
        if match.group(1):
            codes = match.group(1)[2:-1].split(";") if match.group(1) else ["0"]
            for code in codes:
                code = code.strip()
                if not code or code == "0":
                    current_fg = None
                    current_bg = None
                    current_bold = False
                elif code == "1":
                    current_bold = True
                elif code.isdigit():
                    code_int = int(code)
                    if 30 <= code_int <= 37:
                        current_fg = ansi_to_rgb(code_int - 30 + 8)
                    elif 90 <= code_int <= 97:
                        current_fg = ansi_to_rgb(code_int - 90)

            pos = match.end()
            continue

        pos = match.end()

    # Handle remaining text without escape codes
    if pos < len(line):
        text = html.escape(line[pos:])
        if text:
            style = _build_style(current_fg, current_bg, current_bold)
            result += f"<span{style}>{text}</span>"

    return result or html.escape(line)


def _build_style(
    fg: tuple[int, int, int] | None, bg: tuple[int, int, int] | None, bold: bool
) -> str:
    """Build CSS style string from color values."""
    styles = []
    if fg:
        styles.append(f"color: rgb({fg[0]},{fg[1]},{fg[2]})")
    if bg:
        styles.append(f"background-color: rgb({bg[0]},{bg[1]},{bg[2]})")
    if bold:
        styles.append("font-weight: bold")
    if not styles:
        return ""
    return f' style="{"; ".join(styles)}"'


def render_frame_to_html(frame: list[str], frame_number: int = 0) -> str:
    """Render a complete frame (list of lines) to HTML."""
    html_lines = []
    for i, line in enumerate(frame):
        # Strip ANSI cursor positioning but preserve colors
        clean_line = (
            line.replace("\x1b[1;1H", "")
            .replace("\x1b[2;1H", "")
            .replace("\x1b[3;1H", "")
        )
        rendered = render_line_to_html(clean_line)
        html_lines.append(f'<div class="frame-line" data-line="{i}">{rendered}</div>')

    return f"""<div class="frame" id="frame-{frame_number}">
  <div class="frame-header">Frame {frame_number} ({len(frame)} lines)</div>
  <div class="frame-content">
    {"".join(html_lines)}
  </div>
</div>"""


def generate_test_report(
    test_name: str,
    frames: list[list[str]],
    status: str = "PASS",
    duration_ms: float = 0.0,
    metadata: dict[str, Any] | None = None,
) -> str:
    """Generate HTML report for a single test."""
    frames_html = ""
    for i, frame in enumerate(frames):
        frames_html += render_frame_to_html(frame, i)

    metadata_html = ""
    if metadata:
        metadata_html = '<div class="metadata">'
        for key, value in metadata.items():
            metadata_html += f'<div class="meta-row"><span class="meta-key">{key}:</span> <span class="meta-value">{value}</span></div>'
        metadata_html += "</div>"

    status_class = "pass" if status == "PASS" else "fail"

    return f"""<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>{test_name} - Acceptance Test Report</title>
  <style>
    body {{
      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
      background: #1a1a2e;
      color: #eee;
      margin: 0;
      padding: 20px;
    }}
    .test-report {{
      max-width: 1200px;
      margin: 0 auto;
    }}
    .test-header {{
      background: #16213e;
      padding: 20px;
      border-radius: 8px;
      margin-bottom: 20px;
      display: flex;
      justify-content: space-between;
      align-items: center;
    }}
    .test-name {{
      font-size: 24px;
      font-weight: bold;
      color: #fff;
    }}
    .status {{
      padding: 8px 16px;
      border-radius: 4px;
      font-weight: bold;
    }}
    .status.pass {{
      background: #28a745;
      color: white;
    }}
    .status.fail {{
      background: #dc3545;
      color: white;
    }}
    .frame {{
      background: #0f0f1a;
      border: 1px solid #333;
      border-radius: 4px;
      margin-bottom: 20px;
      overflow: hidden;
    }}
    .frame-header {{
      background: #16213e;
      padding: 10px 15px;
      font-size: 14px;
      color: #888;
      border-bottom: 1px solid #333;
    }}
    .frame-content {{
      padding: 15px;
      font-family: 'Fira Code', 'Consolas', 'Monaco', monospace;
      font-size: 13px;
      line-height: 1.4;
      white-space: pre;
      overflow-x: auto;
    }}
    .frame-line {{
      min-height: 1.4em;
    }}
    .metadata {{
      background: #16213e;
      padding: 15px;
      border-radius: 4px;
      margin-bottom: 20px;
    }}
    .meta-row {{
      display: flex;
      gap: 20px;
      font-size: 14px;
    }}
    .meta-key {{
      color: #888;
    }}
    .meta-value {{
      color: #fff;
    }}
    .footer {{
      text-align: center;
      color: #666;
      font-size: 12px;
      margin-top: 40px;
    }}
  </style>
</head>
<body>
  <div class="test-report">
    <div class="test-header">
      <div class="test-name">{test_name}</div>
      <div class="status {status_class}">{status}</div>
    </div>
    {metadata_html}
    {frames_html}
    <div class="footer">
      Generated: {datetime.now().isoformat()}
    </div>
  </div>
</body>
</html>"""


def save_report(
    test_name: str,
    frames: list[list[str]],
    output_dir: str = "test-reports",
    status: str = "PASS",
    duration_ms: float = 0.0,
    metadata: dict[str, Any] | None = None,
) -> str:
    """Save HTML report to disk and return the file path."""
    output_path = Path(output_dir)
    output_path.mkdir(parents=True, exist_ok=True)

    # Sanitize test name for filename
    safe_name = "".join(c if c.isalnum() or c in "-_" else "_" for c in test_name)
    filename = f"{safe_name}.html"
    filepath = output_path / filename

    html_content = generate_test_report(
        test_name, frames, status, duration_ms, metadata
    )
    filepath.write_text(html_content)

    return str(filepath)


def save_index_report(
    reports: list[dict[str, Any]],
    output_dir: str = "test-reports",
) -> str:
    """Generate an index HTML page linking to all test reports."""
    output_path = Path(output_dir)
    output_path.mkdir(parents=True, exist_ok=True)

    rows = ""
    for report in reports:
        safe_name = "".join(
            c if c.isalnum() or c in "-_" else "_" for c in report["test_name"]
        )
        filename = f"{safe_name}.html"
        status_class = "pass" if report["status"] == "PASS" else "fail"
        rows += f"""
        <tr>
          <td><a href="{filename}">{report["test_name"]}</a></td>
          <td class="status {status_class}">{report["status"]}</td>
          <td>{report.get("duration_ms", 0):.1f}ms</td>
          <td>{report.get("frame_count", 0)}</td>
        </tr>
        """

    html = f"""<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Acceptance Test Reports</title>
  <style>
    body {{
      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
      background: #1a1a2e;
      color: #eee;
      margin: 0;
      padding: 40px;
    }}
    h1 {{
      color: #fff;
      margin-bottom: 30px;
    }}
    table {{
      width: 100%;
      border-collapse: collapse;
    }}
    th, td {{
      padding: 12px;
      text-align: left;
      border-bottom: 1px solid #333;
    }}
    th {{
      background: #16213e;
      color: #888;
      font-weight: normal;
    }}
    a {{
      color: #4dabf7;
      text-decoration: none;
    }}
    a:hover {{
      text-decoration: underline;
    }}
    .status {{
      padding: 4px 8px;
      border-radius: 4px;
      font-size: 12px;
      font-weight: bold;
    }}
    .status.pass {{
      background: #28a745;
      color: white;
    }}
    .status.fail {{
      background: #dc3545;
      color: white;
    }}
  </style>
</head>
<body>
  <h1>Acceptance Test Reports</h1>
  <table>
    <thead>
      <tr>
        <th>Test</th>
        <th>Status</th>
        <th>Duration</th>
        <th>Frames</th>
      </tr>
    </thead>
    <tbody>
      {rows}
    </tbody>
  </table>
</body>
</html>"""

    index_path = output_path / "index.html"
    index_path.write_text(html)
    return str(index_path)
