"""PR comment formatting for policy check results.

This module provides functions for generating GitHub-flavored Markdown comments
to display policy evaluation results on pull requests. The formatted comments
include:

- Pass/fail status with visual indicators
- Violation tables with severity, file, line, and message
- "How to Fix" sections grouped by rule with remediation guidance
- Metadata footer with evaluation ID and timing

All comments include a hidden HTML marker (COMMENT_MARKER) that enables
the GitHubAPI to find and update existing bot comments instead of creating
duplicates.

Example output for a failed check::

    ## X MergeGuide Policy Check: Failed

    Found **2** errors, **1** warning in this pull request.

    ### Violations

    | Severity | File | Line | Rule | Message |
    |----------|------|------|------|---------|
    | error | `src/main.py` | 42 | `SEC-001` | Hardcoded secret detected |
    ...

    ### How to Fix

    **No Hardcoded Secrets** (`SEC-001`) - 2 occurrences
    > Use environment variables or a secrets manager instead.

Typical usage:
    >>> from mergeguide.github.comments import format_pr_comment
    >>> body = format_pr_comment(policy_output, "abc-123")
    >>> await github.post_comment(installation_id, owner, repo, pr, body)
"""

from __future__ import annotations

from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from mergeguide.engine.evaluator import PolicyOutput
    from mergeguide.engine.reporter import Violation

# HTML comment marker used to identify MergeGuide bot comments.
# This invisible marker enables finding and updating existing comments
# instead of posting duplicates on each push.
COMMENT_MARKER = "<!-- mergeguide-policy-check -->"


def format_pr_comment(output: PolicyOutput, evaluation_id: str) -> str:
    """Generate a full Markdown PR comment from policy evaluation results.

    Creates a comprehensive comment displaying the evaluation outcome including:
    - Pass/fail header with emoji indicator
    - Violation counts by severity
    - Detailed violations table (for failures)
    - Remediation guidance grouped by rule
    - Metadata footer with evaluation ID and timing

    The comment always starts with COMMENT_MARKER to enable idempotent
    comment updates.

    Args:
        output: PolicyOutput from the evaluation containing violations,
            files checked, duration, and pass/fail status.
        evaluation_id: Unique evaluation ID for tracking/debugging.

    Returns:
        Complete Markdown-formatted comment body ready for posting to GitHub.
    """
    lines = [COMMENT_MARKER, ""]

    # Header with pass/fail status and emoji indicator
    if output.passed:
        lines.append("## ✅ MergeGuide Policy Check: Passed")
        lines.append("")
        lines.append("No policy violations found in this pull request.")
    else:
        lines.append("## ❌ MergeGuide Policy Check: Failed")
        lines.append("")

        # Count violations by severity level
        errors = sum(1 for v in output.violations if v.severity == "error")
        warnings = sum(1 for v in output.violations if v.severity == "warning")
        infos = sum(1 for v in output.violations if v.severity == "info")

        # Build human-readable summary with proper pluralization
        summary_parts = []
        if errors:
            summary_parts.append(f"**{errors}** error{'s' if errors != 1 else ''}")
        if warnings:
            summary_parts.append(f"**{warnings}** warning{'s' if warnings != 1 else ''}")
        if infos:
            summary_parts.append(f"**{infos}** info{'s' if infos != 1 else ''}")

        lines.append(f"Found {', '.join(summary_parts)} in this pull request.")
        lines.append("")

        # Build violations table sorted by severity (errors first)
        lines.append("### Violations")
        lines.append("")
        lines.append("| Severity | File | Line | Rule | Message |")
        lines.append("|----------|------|------|------|---------|")

        for v in sorted(output.violations, key=_violation_sort_key):
            severity_icon = _severity_icon(v.severity)
            file_link = _format_file_link(v.file_path, v.line_no)
            # Truncate long messages to keep the table readable
            message = _escape_markdown(v.message[:80])
            if len(v.message) > 80:
                message += "..."

            lines.append(
                f"| {severity_icon} {v.severity} | {file_link} | {v.line_no or '-'} | "
                f"`{v.rule_id}` | {message} |"
            )

        # Add "How to Fix" section with remediation guidance
        remediations = _group_by_remediation(output.violations)
        if remediations:
            lines.append("")
            lines.append("### How to Fix")
            lines.append("")

            for rule_id, (rule_name, remediation, count) in remediations.items():
                plural = "s" if count != 1 else ""
                lines.append(f"**{rule_name}** (`{rule_id}`) - {count} occurrence{plural}")
                if remediation:
                    lines.append(f"> {remediation}")
                lines.append("")

    # Footer with evaluation metadata for debugging/auditing
    lines.append("---")
    lines.append(
        f"<sub>Evaluation ID: `{evaluation_id}` | "
        f"Files checked: {len(output.files_checked)} | "
        f"Duration: {output.duration_ms}ms</sub>"
    )

    return "\n".join(lines)


def format_pending_comment() -> str:
    """Generate a comment indicating that policy evaluation is in progress.

    Creates a simple "loading" comment that can be posted when evaluation
    starts and later updated with full results. Useful for long-running
    evaluations to provide immediate feedback.

    Returns:
        Markdown-formatted comment body with "In Progress" message.
    """
    lines = [
        COMMENT_MARKER,
        "",
        "## ⏳ MergeGuide Policy Check: In Progress",
        "",
        "Evaluating pull request against configured policies...",
    ]
    return "\n".join(lines)


def format_error_comment(error_message: str, evaluation_id: str | None = None) -> str:
    """Generate a comment indicating that an error occurred during evaluation.

    Creates an error comment that shows the error message and guidance for
    users. Used when the policy check itself fails (infrastructure error),
    as opposed to policy violations (which use format_pr_comment).

    Args:
        error_message: Description of what went wrong. Will be displayed
            in a code block for readability.
        evaluation_id: Optional evaluation ID for tracking. If provided,
            will be shown in a footer.

    Returns:
        Markdown-formatted comment body with error details and guidance.
    """
    lines = [
        COMMENT_MARKER,
        "",
        "## ⚠️ MergeGuide Policy Check: Error",
        "",
        "An error occurred while evaluating this pull request:",
        "",
        f"```\n{error_message}\n```",
        "",
        "Please try again or contact your administrator if the issue persists.",
    ]

    if evaluation_id:
        lines.append("")
        lines.append("---")
        lines.append(f"<sub>Evaluation ID: `{evaluation_id}`</sub>")

    return "\n".join(lines)


def _severity_icon(severity: str) -> str:
    """Get the colored circle emoji icon for a severity level.

    Args:
        severity: One of 'error', 'warning', or 'info'.

    Returns:
        Colored circle emoji: red for error, yellow for warning, blue for info.
        Returns white circle for unknown severity levels.
    """
    icons = {
        "error": "🔴",
        "warning": "🟡",
        "info": "🔵",
    }
    return icons.get(severity, "⚪")


def _violation_sort_key(v: Violation) -> tuple:
    """Generate a sort key for ordering violations in the results table.

    Violations are sorted by:
    1. Severity (errors first, then warnings, then info)
    2. File path (alphabetically)
    3. Line number (numerically)

    Args:
        v: A Violation instance to generate a sort key for.

    Returns:
        Tuple suitable for use as a sort key.
    """
    severity_order = {"error": 0, "warning": 1, "info": 2}
    return (severity_order.get(v.severity, 3), v.file_path, v.line_no or 0)


def _format_file_link(file_path: str, line_no: int | None) -> str:
    """Format a file path for display in the violations table.

    Returns the file path wrapped in backticks for code formatting.
    Long paths (>50 chars) are truncated from the beginning with ellipsis.

    Note: We show the filename in backticks rather than a full link since
    the comment is on the PR and users can find files in "Files changed".

    Args:
        file_path: Path to the file containing the violation.
        line_no: Optional line number (not used in current implementation).

    Returns:
        Formatted file path string suitable for Markdown tables.
    """
    if not file_path:
        return "-"

    # Truncate long paths from the beginning to show the filename
    if len(file_path) > 50:
        return "`..." + file_path[-47:] + "`"

    return f"`{file_path}`"


def _escape_markdown(text: str) -> str:
    """Escape special Markdown characters in text for safe table display.

    Currently escapes:
    - Pipe characters (|) which would break table formatting
    - Backticks (`) which would create unintended code spans

    Args:
        text: Raw text that may contain Markdown special characters.

    Returns:
        Text with special characters escaped using backslashes.
    """
    # Escape pipe characters for table cells
    text = text.replace("|", "\\|")
    # Escape backticks
    text = text.replace("`", "\\`")
    return text


def _group_by_remediation(
    violations: list[Violation],
) -> dict[str, tuple[str, str, int]]:
    """Group violations by rule ID for the "How to Fix" section.

    Consolidates multiple violations of the same rule into a single
    entry with a count, to avoid repetitive remediation text.

    Args:
        violations: List of Violation instances from the evaluation.

    Returns:
        Dict mapping rule_id to a tuple of (rule_name, remediation_text, count).
        The remediation_text may be empty if the rule has no remediation defined.
    """
    groups: dict[str, tuple[str, str, int]] = {}

    for v in violations:
        if v.rule_id in groups:
            # Increment count for existing rule
            name, remediation, count = groups[v.rule_id]
            groups[v.rule_id] = (name, remediation, count + 1)
        else:
            # First occurrence of this rule
            groups[v.rule_id] = (v.rule_name, v.remediation or "", 1)

    return groups
