"""Compliance report generator — PDF and HTML output."""

from __future__ import annotations

import hashlib
import json
import subprocess
from dataclasses import dataclass, field
from datetime import datetime
from pathlib import Path
from typing import Any

from apiposture_pro.enterprise.compliance.compliance_score import ComplianceScore
from apiposture_pro.enterprise.compliance.remediation_map import get_mapping

SCOPE_DISCLAIMER = (
    "Scope: API authorization and security misconfiguration controls. "
    "This report covers a subset of {framework} controls. "
    "See your audit firm for full control mapping guidance."
)

FRAMEWORK_DISPLAY = {
    "soc2": "SOC 2",
    "iso27001": "ISO 27001",
    "all": "SOC 2 + ISO 27001",
}


@dataclass
class ReportFinding:
    """A finding enriched with framework controls and remediation guidance."""

    rule_id: str
    severity: str
    message: str
    location: str
    framework_controls: list[str] = field(default_factory=list)
    remediation_guidance: str = ""


@dataclass
class ReportSummary:
    """Summary section of a compliance report."""

    total_endpoints: int
    total_findings: int
    scanned_path: str
    git_commit: str | None


@dataclass
class ComplianceReport:
    """Full compliance report data model."""

    scan_id: str
    timestamp: datetime
    operator: str | None
    framework: str
    compliance_score: ComplianceScore
    findings: list[ReportFinding]
    summary: ReportSummary
    checksum: str = ""

    def to_dict(self) -> dict[str, Any]:
        """Serialise to a plain dict (canonical, sorted keys)."""
        pb = self.compliance_score.penalty_breakdown
        return {
            "checksum": self.checksum,
            "compliance_score": {
                "penalty_breakdown": {
                    "critical": pb.critical,
                    "high": pb.high,
                    "low": pb.low,
                    "medium": pb.medium,
                },
                "score": self.compliance_score.score,
                "trend": self.compliance_score.trend,
            },
            "findings": [
                {
                    "framework_controls": f.framework_controls,
                    "location": f.location,
                    "message": f.message,
                    "remediation_guidance": f.remediation_guidance,
                    "rule_id": f.rule_id,
                    "severity": f.severity,
                }
                for f in self.findings
            ],
            "framework": self.framework,
            "operator": self.operator,
            "scan_id": self.scan_id,
            "summary": {
                "git_commit": self.summary.git_commit,
                "scanned_path": self.summary.scanned_path,
                "total_endpoints": self.summary.total_endpoints,
                "total_findings": self.summary.total_findings,
            },
            "timestamp": self.timestamp.isoformat(),
        }


def _get_git_commit(path: Path) -> str | None:
    """Try to get current git commit SHA."""
    try:
        result = subprocess.run(
            ["git", "rev-parse", "--short", "HEAD"],
            capture_output=True,
            text=True,
            cwd=str(path),
            timeout=5,
        )
        if result.returncode == 0:
            return result.stdout.strip()
    except Exception:
        pass
    return None


def _enrich_findings(raw_findings: list[dict[str, Any]], framework: str) -> list[ReportFinding]:
    """Enrich raw findings with framework controls and remediation guidance."""
    enriched = []
    for f in raw_findings:
        rule_id = f.get("rule_id", "")
        mapping = get_mapping(rule_id)
        if mapping:
            controls = mapping.framework_controls(framework)
            guidance = mapping.remediation
        else:
            controls = []
            guidance = ""

        enriched.append(
            ReportFinding(
                rule_id=rule_id,
                severity=f.get("severity", "info"),
                message=f.get("message", ""),
                location=f.get("location", ""),
                framework_controls=controls,
                remediation_guidance=guidance,
            )
        )
    return enriched


def build_report(
    scan_id: str,
    timestamp: datetime,
    scanned_path: str,
    raw_findings: list[dict[str, Any]],
    total_endpoints: int,
    compliance_score: ComplianceScore,
    framework: str,
    operator: str | None = None,
    git_commit: str | None = None,
) -> ComplianceReport:
    """Build a ComplianceReport from scan data."""
    enriched = _enrich_findings(raw_findings, framework)

    summary = ReportSummary(
        total_endpoints=total_endpoints,
        total_findings=len(enriched),
        scanned_path=scanned_path,
        git_commit=git_commit,
    )

    report = ComplianceReport(
        scan_id=scan_id,
        timestamp=timestamp,
        operator=operator,
        framework=framework,
        compliance_score=compliance_score,
        findings=enriched,
        summary=summary,
    )

    # Compute checksum of canonical JSON (without checksum field)
    canonical = json.dumps(report.to_dict(), sort_keys=True, separators=(",", ":"))
    report.checksum = hashlib.sha256(canonical.encode("utf-8")).hexdigest()
    return report


def generate_html(report: ComplianceReport, output_path: Path) -> None:
    """Generate HTML compliance report."""
    framework_display = FRAMEWORK_DISPLAY.get(report.framework, report.framework.upper())
    disclaimer = SCOPE_DISCLAIMER.format(framework=framework_display)

    # Severity color map
    sev_colors = {
        "critical": "#c0392b",
        "high": "#e74c3c",
        "medium": "#e67e22",
        "low": "#3498db",
        "info": "#7f8c8d",
    }

    findings_rows = ""
    for f in report.findings:
        sev = f.severity.lower()
        color = sev_colors.get(sev, "#7f8c8d")
        controls = ", ".join(f.framework_controls) or "—"
        findings_rows += f"""
        <tr>
          <td><span style="color:{color};font-weight:bold">{f.severity.upper()}</span></td>
          <td>{f.rule_id}</td>
          <td>{f.message}</td>
          <td style="font-size:0.85em">{f.location}</td>
          <td style="font-size:0.85em">{controls}</td>
          <td style="font-size:0.85em">{f.remediation_guidance}</td>
        </tr>"""

    pb = report.compliance_score.penalty_breakdown
    score = report.compliance_score.score
    trend_sym = {"up": "↑", "down": "↓", "stable": "→"}.get(report.compliance_score.trend, "→")
    trend_color = {"up": "green", "down": "red", "stable": "gray"}.get(
        report.compliance_score.trend, "gray"
    )

    operator_line = f"<p><strong>Operator:</strong> {report.operator}</p>" if report.operator else ""
    git_line = (
        f"<p><strong>Git Commit:</strong> {report.summary.git_commit}</p>"
        if report.summary.git_commit
        else ""
    )

    html = f"""<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>ApiPosture Compliance Report — {framework_display}</title>
  <style>
    body {{ font-family: Arial, sans-serif; margin: 40px; color: #333; }}
    h1 {{ color: #2c3e50; }}
    h2 {{ color: #34495e; border-bottom: 1px solid #ccc; padding-bottom: 6px; }}
    .disclaimer {{ background: #fff3cd; border: 1px solid #ffc107; padding: 12px; border-radius: 4px; margin-bottom: 20px; font-size: 0.9em; }}
    .score-box {{ display: inline-block; padding: 12px 24px; border-radius: 8px; background: #ecf0f1; margin-bottom: 20px; }}
    .score-num {{ font-size: 2.5em; font-weight: bold; color: #2c3e50; }}
    table {{ border-collapse: collapse; width: 100%; font-size: 0.9em; }}
    th {{ background: #2c3e50; color: white; padding: 8px; text-align: left; }}
    td {{ border: 1px solid #ddd; padding: 8px; vertical-align: top; }}
    tr:nth-child(even) {{ background: #f9f9f9; }}
    .footer {{ margin-top: 30px; font-size: 0.8em; color: #7f8c8d; border-top: 1px solid #eee; padding-top: 10px; }}
  </style>
</head>
<body>
  <h1>ApiPosture Compliance Report</h1>
  <div class="disclaimer">{disclaimer}</div>

  <h2>Scan Summary</h2>
  <p><strong>Framework:</strong> {framework_display}</p>
  <p><strong>Scan ID:</strong> {report.scan_id}</p>
  <p><strong>Timestamp:</strong> {report.timestamp.strftime("%Y-%m-%d %H:%M:%S UTC")}</p>
  <p><strong>Scanned Path:</strong> {report.summary.scanned_path}</p>
  {operator_line}
  {git_line}
  <p><strong>Total Endpoints:</strong> {report.summary.total_endpoints}</p>
  <p><strong>Total Findings:</strong> {report.summary.total_findings}</p>

  <h2>Compliance Score</h2>
  <div class="score-box">
    <div class="score-num">{score}/100</div>
    <div>Trend: <span style="color:{trend_color}">{trend_sym} {report.compliance_score.trend.upper()}</span></div>
  </div>
  <p><strong>Penalty Breakdown:</strong>
    Critical: {pb.critical} pts &nbsp;|&nbsp;
    High: {pb.high} pts &nbsp;|&nbsp;
    Medium: {pb.medium} pts &nbsp;|&nbsp;
    Low: {pb.low} pts
  </p>

  <h2>Findings ({len(report.findings)})</h2>
  <table>
    <tr>
      <th>Severity</th>
      <th>Rule</th>
      <th>Message</th>
      <th>Location</th>
      <th>Controls</th>
      <th>Remediation</th>
    </tr>
    {findings_rows if findings_rows else "<tr><td colspan='6' style='text-align:center'>No findings</td></tr>"}
  </table>

  <div class="footer">
    <p>Checksum (SHA-256): {report.checksum}</p>
    <p>Generated by ApiPosturePro</p>
  </div>
</body>
</html>"""

    output_path.write_text(html, encoding="utf-8")


def generate_pdf(report: ComplianceReport, output_path: Path) -> None:
    """Generate PDF compliance report using ReportLab."""
    from reportlab.lib import colors
    from reportlab.lib.pagesizes import A4
    from reportlab.lib.styles import ParagraphStyle, getSampleStyleSheet
    from reportlab.lib.units import cm
    from reportlab.platypus import (
        HRFlowable,
        Paragraph,
        SimpleDocTemplate,
        Spacer,
        Table,
        TableStyle,
    )

    framework_display = FRAMEWORK_DISPLAY.get(report.framework, report.framework.upper())
    disclaimer = SCOPE_DISCLAIMER.format(framework=framework_display)

    doc = SimpleDocTemplate(
        str(output_path),
        pagesize=A4,
        rightMargin=2 * cm,
        leftMargin=2 * cm,
        topMargin=2 * cm,
        bottomMargin=2 * cm,
    )
    styles = getSampleStyleSheet()
    story = []

    # Title
    title_style = ParagraphStyle(
        "Title", parent=styles["Title"], fontSize=18, spaceAfter=12
    )
    story.append(Paragraph("ApiPosture Compliance Report", title_style))
    story.append(Paragraph(f"Framework: {framework_display}", styles["Normal"]))
    story.append(Spacer(1, 0.3 * cm))

    # Disclaimer
    disclaimer_style = ParagraphStyle(
        "Disclaimer",
        parent=styles["Normal"],
        backColor=colors.lightyellow,
        borderColor=colors.orange,
        borderWidth=1,
        borderPadding=6,
        fontSize=9,
        spaceAfter=12,
    )
    story.append(Paragraph(disclaimer, disclaimer_style))

    # Summary
    story.append(Paragraph("Scan Summary", styles["Heading2"]))
    story.append(HRFlowable(width="100%"))
    story.append(Spacer(1, 0.2 * cm))
    story.append(Paragraph(f"<b>Scan ID:</b> {report.scan_id}", styles["Normal"]))
    story.append(
        Paragraph(
            f"<b>Timestamp:</b> {report.timestamp.strftime('%Y-%m-%d %H:%M:%S UTC')}",
            styles["Normal"],
        )
    )
    story.append(
        Paragraph(f"<b>Scanned Path:</b> {report.summary.scanned_path}", styles["Normal"])
    )
    if report.operator:
        story.append(Paragraph(f"<b>Operator:</b> {report.operator}", styles["Normal"]))
    if report.summary.git_commit:
        story.append(
            Paragraph(f"<b>Git Commit:</b> {report.summary.git_commit}", styles["Normal"])
        )
    story.append(
        Paragraph(f"<b>Total Endpoints:</b> {report.summary.total_endpoints}", styles["Normal"])
    )
    story.append(
        Paragraph(f"<b>Total Findings:</b> {report.summary.total_findings}", styles["Normal"])
    )
    story.append(Spacer(1, 0.4 * cm))

    # Compliance Score
    story.append(Paragraph("Compliance Score", styles["Heading2"]))
    story.append(HRFlowable(width="100%"))
    story.append(Spacer(1, 0.2 * cm))
    pb = report.compliance_score.penalty_breakdown
    score = report.compliance_score.score
    trend = report.compliance_score.trend
    story.append(
        Paragraph(f"<b>Score: {score}/100</b>  |  Trend: {trend.upper()}", styles["Normal"])
    )
    story.append(
        Paragraph(
            f"Penalty — Critical: {pb.critical} pts | High: {pb.high} pts | "
            f"Medium: {pb.medium} pts | Low: {pb.low} pts",
            styles["Normal"],
        )
    )
    story.append(Spacer(1, 0.4 * cm))

    # Findings table
    story.append(Paragraph(f"Findings ({len(report.findings)})", styles["Heading2"]))
    story.append(HRFlowable(width="100%"))
    story.append(Spacer(1, 0.2 * cm))

    if report.findings:
        table_data = [["Sev", "Rule", "Message", "Controls", "Remediation"]]
        for f in report.findings:
            controls = "\n".join(f.framework_controls) if f.framework_controls else "—"
            msg = f.message[:60] + "…" if len(f.message) > 60 else f.message
            table_data.append(
                [f.severity.upper(), f.rule_id, msg, controls, f.remediation_guidance[:60]]
            )

        col_widths = [1.5 * cm, 1.5 * cm, 5 * cm, 3.5 * cm, 5 * cm]
        tbl = Table(table_data, colWidths=col_widths, repeatRows=1)

        tbl_style = TableStyle(
            [
                ("BACKGROUND", (0, 0), (-1, 0), colors.HexColor("#2c3e50")),
                ("TEXTCOLOR", (0, 0), (-1, 0), colors.white),
                ("FONTSIZE", (0, 0), (-1, -1), 7),
                ("VALIGN", (0, 0), (-1, -1), "TOP"),
                ("GRID", (0, 0), (-1, -1), 0.25, colors.grey),
                ("ROWBACKGROUNDS", (0, 1), (-1, -1), [colors.white, colors.HexColor("#f9f9f9")]),
            ]
        )
        tbl.setStyle(tbl_style)
        story.append(tbl)
    else:
        story.append(Paragraph("No findings.", styles["Normal"]))

    story.append(Spacer(1, 0.5 * cm))

    # Footer with checksum
    footer_style = ParagraphStyle(
        "Footer", parent=styles["Normal"], fontSize=8, textColor=colors.grey
    )
    story.append(HRFlowable(width="100%"))
    story.append(Paragraph(f"Checksum (SHA-256): {report.checksum}", footer_style))
    story.append(Paragraph("Generated by ApiPosturePro", footer_style))

    doc.build(story)
