"""Markdown formatter for diagnostic output.

Generates human-readable Markdown tables with register data,
comparison results, and summary statistics.
"""

from __future__ import annotations

from pylxpweb import __version__
from pylxpweb.cli.collectors.base import (
    CollectionResult,
    ComparisonResult,
    RegisterMismatch,
)
from pylxpweb.cli.utils.sanitize import sanitize_serial

from .base import DiagnosticData


class MarkdownFormatter:
    """Format diagnostic data as Markdown.

    Produces a Markdown document with:
    - Device information (serial, firmware)
    - Register tables (input and holding)
    - Comparison tables (if multiple sources)
    - Summary statistics

    Tables use GitHub-flavored Markdown for compatibility with issue reports.
    """

    def __init__(
        self,
        sanitize: bool = True,
        max_registers_per_table: int = 100,
    ) -> None:
        """Initialize Markdown formatter.

        Args:
            sanitize: Whether to mask sensitive data
            max_registers_per_table: Max registers before table split
        """
        self._sanitize = sanitize
        self._max_registers = max_registers_per_table

    @property
    def file_extension(self) -> str:
        """Return file extension."""
        return "md"

    @property
    def is_binary(self) -> bool:
        """Return False - Markdown is text."""
        return False

    def format(self, data: DiagnosticData) -> str:
        """Generate Markdown output.

        Args:
            data: Diagnostic data to format

        Returns:
            Markdown string
        """
        lines: list[str] = []

        # Title
        lines.append("# Modbus Diagnostic Report")
        lines.append("")
        lines.append(f"**Serial:** {self._sanitize_serial(data.serial_number)}")
        if data.firmware_version:
            lines.append(f"**Firmware:** {data.firmware_version}")
        lines.append(f"**Generated:** {data.timestamp.strftime('%Y-%m-%d %H:%M:%S %Z')}")
        lines.append(f"**Tool Version:** pylxpweb v{__version__}")
        lines.append("")

        # Collection summaries
        lines.append("## Collections")
        lines.append("")
        for collection in data.collections:
            lines.extend(self._format_collection_summary(collection))
        lines.append("")

        # Comparison (if available)
        if data.comparison:
            lines.extend(self._format_comparison(data.comparison))
            lines.append("")

        # Register tables
        if data.collections:
            lines.extend(self._format_register_tables(data))

        # Footer
        lines.append("---")
        lines.append(f"*Generated by pylxpweb v{__version__}*")

        return "\n".join(lines)

    def _format_collection_summary(
        self,
        collection: CollectionResult,
    ) -> list[str]:
        """Format summary for a single collection."""

        lines = [
            f"### {collection.source.upper()} Collection",
            "",
            f"- **Serial:** {self._sanitize_serial(collection.serial_number)}",
            f"- **Timestamp:** {collection.timestamp.strftime('%Y-%m-%d %H:%M:%S')}",
            f"- **Duration:** {collection.duration_seconds:.2f}s",
            f"- **Input Registers:** {collection.input_register_count()} "
            f"({collection.input_nonzero_count()} non-zero)",
            f"- **Holding Registers:** {collection.holding_register_count()} "
            f"({collection.holding_nonzero_count()} non-zero)",
        ]

        if collection.connection_params:
            params_str = ", ".join(
                f"{k}={self._sanitize_param(k, v)}" for k, v in collection.connection_params.items()
            )
            lines.append(f"- **Connection:** {params_str}")

        if collection.errors:
            lines.append(f"- **Errors:** {len(collection.errors)}")
            for error in collection.errors[:5]:  # Show first 5
                lines.append(f"  - {error}")
            if len(collection.errors) > 5:
                lines.append(f"  - ... and {len(collection.errors) - 5} more")

        lines.append("")
        return lines

    def _format_comparison(self, comparison: ComparisonResult) -> list[str]:
        """Format comparison results."""

        lines = [
            "## Comparison Results",
            "",
            f"**Sources:** {' vs '.join(comparison.sources)}",
            f"**Status:** {'Match' if comparison.is_match() else 'Mismatches Found'}",
            "",
        ]

        # Summary table
        lines.extend(
            [
                "| Register Type | Matches | Mismatches |",
                "|---------------|---------|------------|",
                f"| Input | {comparison.input_match_count} | {len(comparison.input_mismatches)} |",
                f"| Holding | {comparison.holding_match_count} | "
                f"{len(comparison.holding_mismatches)} |",
                "",
            ]
        )

        # Mismatch details
        if comparison.input_mismatches:
            lines.append("### Input Register Mismatches")
            lines.append("")
            lines.extend(self._format_mismatch_table(comparison.input_mismatches))
            lines.append("")

        if comparison.holding_mismatches:
            lines.append("### Holding Register Mismatches")
            lines.append("")
            lines.extend(self._format_mismatch_table(comparison.holding_mismatches))
            lines.append("")

        return lines

    def _format_mismatch_table(
        self,
        mismatches: list[RegisterMismatch],
    ) -> list[str]:
        """Format mismatch table."""

        if not mismatches:
            return ["*No mismatches*"]

        # Get source names from first mismatch
        src_a = mismatches[0].source_a
        src_b = mismatches[0].source_b

        lines = [
            f"| Address | {src_a} | {src_b} | Diff |",
            "|---------|--------|--------|------|",
        ]

        for m in mismatches[:50]:  # Limit to 50 rows
            val_a = str(m.value_a) if m.value_a is not None else "N/A"
            val_b = str(m.value_b) if m.value_b is not None else "N/A"

            # Calculate diff if both are numeric
            diff = ""
            if m.value_a is not None and m.value_b is not None:
                diff = str(m.value_a - m.value_b)

            lines.append(f"| {m.address} | {val_a} | {val_b} | {diff} |")

        if len(mismatches) > 50:
            lines.append("| ... | ... | ... | ... |")
            lines.append(f"*Showing first 50 of {len(mismatches)} mismatches*")

        return lines

    def _format_register_tables(self, data: DiagnosticData) -> list[str]:
        """Format register data tables."""
        lines = ["## Register Data", ""]

        # For single collection, show full tables
        # For multiple, show side-by-side comparison
        if len(data.collections) == 1:
            collection = data.collections[0]
            lines.extend(self._format_single_register_table("Input", collection.input_registers))
            lines.append("")
            lines.extend(
                self._format_single_register_table("Holding", collection.holding_registers)
            )
        else:
            lines.extend(self._format_multi_register_table("Input", data.collections, "input"))
            lines.append("")
            lines.extend(self._format_multi_register_table("Holding", data.collections, "holding"))

        return lines

    def _format_single_register_table(
        self,
        reg_type: str,
        registers: dict[int, int],
    ) -> list[str]:
        """Format single-source register table."""
        lines = [f"### {reg_type} Registers", ""]

        if not registers:
            lines.append("*No registers collected*")
            return lines

        lines.extend(
            [
                "| Address | Value | Hex |",
                "|---------|-------|-----|",
            ]
        )

        for count, addr in enumerate(sorted(registers.keys())):
            if count >= self._max_registers:
                lines.append("| ... | ... | ... |")
                lines.append(f"*Showing first {self._max_registers} of {len(registers)} registers*")
                break

            val = registers[addr]
            lines.append(f"| {addr} | {val} | 0x{val:04X} |")

        return lines

    def _format_multi_register_table(
        self,
        reg_type: str,
        collections: list[CollectionResult],
        attr: str,
    ) -> list[str]:
        """Format multi-source register comparison table."""

        lines = [f"### {reg_type} Registers", ""]

        # Get all addresses from all collections
        all_addrs: set[int] = set()
        for c in collections:
            regs = c.input_registers if attr == "input" else c.holding_registers
            all_addrs.update(regs.keys())

        if not all_addrs:
            lines.append("*No registers collected*")
            return lines

        # Build header
        sources = [c.source for c in collections]
        header = "| Address | " + " | ".join(sources) + " | Match |"
        separator = "|---------|" + "|".join(["------"] * len(sources)) + "|-------|"
        lines.extend([header, separator])

        for count, addr in enumerate(sorted(all_addrs)):
            if count >= self._max_registers:
                cols = " | ".join(["..."] * len(sources))
                lines.append(f"| ... | {cols} | ... |")
                lines.append(f"*Showing first {self._max_registers} of {len(all_addrs)} registers*")
                break

            values = []
            for c in collections:
                regs = c.input_registers if attr == "input" else c.holding_registers
                val = regs.get(addr)
                values.append(str(val) if val is not None else "N/A")

            # Check if all match
            unique_values = {v for v in values if v != "N/A"}
            match = "Yes" if len(unique_values) <= 1 else "**No**"

            row = f"| {addr} | " + " | ".join(values) + f" | {match} |"
            lines.append(row)

        return lines

    def _sanitize_serial(self, serial: str) -> str:
        """Mask serial number if sanitization is enabled."""
        return sanitize_serial(serial, enabled=self._sanitize)

    def _sanitize_param(self, key: str, value: str | int) -> str:
        """Sanitize a connection parameter value."""
        if not self._sanitize:
            return str(value)

        if key in ("password",):
            return "***"
        if "serial" in key.lower():
            return self._sanitize_serial(str(value))
        return str(value)
