"""Renderer for Nhandu documents."""

from __future__ import annotations

import base64
import html
from pathlib import Path

import mistune
from pygments import highlight as pygments_highlight
from pygments.formatters import HtmlFormatter
from pygments.lexers import get_lexer_by_name
from pygments.styles import get_all_styles

from nhandu.models import (
    CodeBlock,
    ExecutedDocument,
    MarkdownBlock,
)
from nhandu.templates import get_template


def _render_markdown(doc: ExecutedDocument) -> str:
    """Render an executed document to Markdown format."""
    output_parts = []

    # Add frontmatter if it exists
    if doc.metadata.raw:
        import yaml

        output_parts.append("---")
        yaml_output = yaml.dump(doc.metadata.raw, default_flow_style=False)
        if isinstance(yaml_output, str):
            output_parts.append(yaml_output.strip())
        output_parts.append("---")
        output_parts.append("")

    # Render blocks
    for block in doc.blocks:
        if isinstance(block, MarkdownBlock):
            if block.content.strip():
                output_parts.append(block.content)
        elif isinstance(block, CodeBlock):
            if block.has_output:
                output_parts.append(_render_markdown_code_block(block))

    return "\n".join(output_parts)


def _render_markdown_code_block(block: CodeBlock) -> str:
    """Render a code block with its output as Markdown."""
    if block.hidden:
        return ""

    parts = ["```python", block.content.rstrip(), "```"]

    if block.output or block.error or block.figures:
        parts.extend(["", "Output:", "```"])

        if block.output:
            parts.append(block.output)
        if block.error:
            parts.append(f"Error: {block.error}")

        parts.append("```")

        for figure in block.figures:
            parts.extend(["", f"![Figure]({figure})"])

    parts.append("")
    return "\n".join(parts)


class HTMLRenderer:
    """Renders to HTML format."""

    # Default theme for syntax highlighting
    DEFAULT_THEME = "github-dark"

    def __init__(self) -> None:
        """Initialize the HTML renderer."""
        self._current_theme: str | None = None
        self._formatter: HtmlFormatter | None = None
        # Create markdown parser once, reuse for all blocks
        self._markdown_parser = mistune.create_markdown(
            plugins=["table", "strikethrough", "footnotes"]
        )

    def render(self, doc: ExecutedDocument) -> str:
        """Render document to HTML."""
        # Resolve theme from document metadata or use default
        theme = self._resolve_theme(doc.metadata.code_theme)
        self._setup_formatter(theme)

        # Determine page title: metadata > filename > default
        title = self._get_document_title(doc)

        # Load custom CSS if specified
        custom_css = None
        if doc.metadata.template_css:
            css_path = self._resolve_template_css_path(
                doc.metadata.template_css,
                doc.source_path,
            )
            custom_css = css_path.read_text(encoding="utf-8")

        parts = [
            "<!DOCTYPE html>",
            "<html>",
            "<head>",
            "<meta charset='utf-8'>",
            f"<title>{html.escape(title)}</title>",
            "<style>",
            self._get_combined_css(doc.metadata.template, custom_css),
            "</style>",
            "</head>",
            "<body>",
        ]

        # Render blocks
        for block in doc.blocks:
            if isinstance(block, MarkdownBlock):
                # Defensive filter: skip empty markdown blocks
                if block.content.strip():
                    html_output = self._markdown_parser(block.content)
                    if isinstance(html_output, str):
                        parts.append(html_output)
            elif isinstance(block, CodeBlock) and not block.hidden:
                if block.has_output:
                    parts.append(self._render_code_block_html(block))

        # Add footer if enabled
        if doc.metadata.show_footer:
            parts.append(self._render_footer())

        parts.extend(["</body>", "</html>"])
        return "\n".join(parts)

    @staticmethod
    def _resolve_template_css_path(
        template_css: str,
        source_path: Path | None,
    ) -> Path:
        """Resolve custom CSS path with document-relative semantics."""
        css_path = Path(template_css)
        if css_path.is_absolute() or source_path is None:
            return css_path

        return (source_path.parent / css_path).resolve()

    def _get_document_title(self, doc: ExecutedDocument) -> str:
        """
        Get document title with fallback logic.

        Priority:
        1. YAML frontmatter title
        2. Source filename (without extension)
        3. Default "Nhandu Report"

        @param doc: The executed document
        @return: Document title string
        """
        # Use metadata title if available
        if doc.metadata.title:
            return doc.metadata.title

        # Fall back to filename if source path exists
        if doc.source_path:
            # Get filename without extension
            return doc.source_path.stem

        # Final fallback
        return "Nhandu Report"

    def _resolve_theme(self, requested_theme: str | None) -> str:
        """Resolve theme name with validation."""
        theme = requested_theme or self.DEFAULT_THEME

        # Validate theme
        valid_styles = list(get_all_styles())
        if theme not in valid_styles:
            # Try to provide helpful error message
            similar = [
                s for s in valid_styles if theme.lower() in s or s in theme.lower()
            ]
            if similar:
                suggestion = f" Did you mean: {', '.join(similar[:3])}?"
            else:
                top_themes = ", ".join(sorted(valid_styles)[:10])
                suggestion = f" Available themes: {top_themes}..."

            raise ValueError(f"Unknown code theme '{theme}'.{suggestion}")

        return theme

    def _setup_formatter(self, theme: str) -> None:
        """Setup Pygments formatter for the given theme."""
        if self._current_theme != theme:
            self._formatter = HtmlFormatter(
                style=theme,
                cssclass="highlight",
                noclasses=False,
            )
            self._current_theme = theme

    def _get_pygments_css(self) -> str:
        """Generate Pygments CSS for the current theme."""
        if self._formatter is None:
            # Fallback: create formatter with default theme
            self._formatter = HtmlFormatter(
                style=self.DEFAULT_THEME,
                cssclass="highlight",
                noclasses=False,
            )
        result: str = self._formatter.get_style_defs(".highlight")
        return result

    def _get_combined_css(
        self,
        template_name: str | None,
        custom_css: str | None = None,
    ) -> str:
        """Get combined CSS: template + Pygments + optional custom overrides.

        @param template_name: Name of the template to use (None = "default").
        @param custom_css: Optional CSS string to append (from --template-css).
        @return: Combined CSS string.
        """
        name = template_name or "default"
        template = get_template(name)
        pygments_css = self._get_pygments_css()

        parts = [
            template.css,
            f"/* Pygments syntax highlighting */\n{pygments_css}",
        ]
        if custom_css:
            parts.append(f"/* Custom CSS overrides */\n{custom_css}")
        return "\n".join(parts)

    def _render_code_block_html(self, block: CodeBlock) -> str:
        """Render a code block as HTML with Pygments syntax highlighting."""
        parts = ['<div class="code-block">']

        # Code input with syntax highlighting
        highlighted_code = self._highlight_code(block.content.rstrip())
        parts.append(f'<div class="code-input">{highlighted_code}</div>')

        # Output and error can now coexist (e.g. partial output before an error)
        if block.output or block.error or block.figures:
            if block.output:
                parts.append(
                    f'<pre class="code-output">{html.escape(block.output)}</pre>'
                )

            if block.error:
                parts.append(f'<pre class="error">{html.escape(block.error)}</pre>')

            # Figures - embed as base64 data URIs
            for figure in block.figures:
                try:
                    figure_path = Path(figure)
                    if figure_path.exists():
                        image_data = figure_path.read_bytes()

                        # Determine MIME type based on file extension
                        ext = figure_path.suffix.lower()
                        if ext == ".png":
                            mime_type = "image/png"
                        elif ext in (".jpg", ".jpeg"):
                            mime_type = "image/jpeg"
                        elif ext == ".svg":
                            mime_type = "image/svg+xml"
                        else:
                            mime_type = "image/png"  # Default

                        # Encode as base64
                        encoded_image = base64.b64encode(image_data).decode("utf-8")
                        data_uri = f"data:{mime_type};base64,{encoded_image}"
                        parts.append(f'<img src="{data_uri}" alt="Figure">')
                    else:
                        # Fallback to file reference if file doesn't exist
                        parts.append(
                            f'<img src="{html.escape(str(figure))}" alt="Figure">'
                        )
                except Exception:
                    # Fallback to file reference on any error
                    parts.append(f'<img src="{html.escape(str(figure))}" alt="Figure">')

        parts.append("</div>")
        return "\n".join(parts)

    def _highlight_code(self, code: str) -> str:
        """Highlight code using Pygments."""
        if self._formatter is None:
            # Fallback if formatter not setup (shouldn't happen)
            self._setup_formatter(self.DEFAULT_THEME)

        lexer = get_lexer_by_name("python", stripall=False)

        # Ensure formatter is not None for mypy
        formatter = self._formatter if self._formatter is not None else HtmlFormatter()
        result: str = pygments_highlight(code, lexer, formatter)
        return result

    def _render_footer(self) -> str:
        """
        Render the Nhandu attribution footer.

        @return: HTML footer element with link to Nhandu
        """
        return (
            '<footer class="nhandu-footer">\n'
            'Made with <a href="https://pypi.org/project/nhandu" '
            'target="_blank" rel="noopener noreferrer">Nhandu</a>\n'
            "</footer>"
        )


def list_themes() -> list[str]:
    """Return all available syntax highlighting theme names."""
    return sorted(get_all_styles())


def render(doc: ExecutedDocument, format: str | None = None) -> str:
    """Render an executed document."""
    output_format = format or doc.metadata.output

    if output_format == "html":
        return HTMLRenderer().render(doc)

    return _render_markdown(doc)
