"""Compliance report generation — NIS2, DORA, ISO 27001 mapping.

Generates an HTML report mapping Census data to regulatory requirements.
All data comes from the local manifest and integrity check — no API calls.
"""

from __future__ import annotations

import html
from dataclasses import asdict, dataclass, field
from datetime import datetime, timezone
from typing import Optional

from . import __version__
from .evidence import IntegrityReport
from .manifest import Manifest

TEMPLATES = frozenset({"nis2", "dora", "iso27001"})


@dataclass
class RequirementStatus:
    """Status of a single compliance requirement."""

    requirement: str
    framework: str
    article: str
    census_capability: str
    status: str
    detail: str = ""


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

    template: str
    generated_at: str = ""
    census_version: str = ""
    manifest_path: str = ""
    manifest_root: str = ""
    total_files: int = 0
    attested_files: int = 0
    pending_files: int = 0
    integrity_ok: Optional[bool] = None
    integrity_modified: int = 0
    integrity_missing: int = 0
    integrity_new: int = 0
    requirements: list[RequirementStatus] = field(default_factory=list)
    passed: int = 0
    warnings: int = 0
    failures: int = 0


_REQUIREMENTS: dict[str, list[dict[str, str]]] = {
    "nis2": [
        {"requirement": "Asset inventory", "article": "Art. 21(2)(a)", "capability": "scan"},
        {"requirement": "Risk assessment", "article": "Art. 21(2)(a)", "capability": "integrity"},
        {"requirement": "Incident handling", "article": "Art. 21(2)(b)", "capability": "compare"},
        {"requirement": "Incident reporting evidence", "article": "Art. 23", "capability": "report"},
        {"requirement": "Supply chain security", "article": "Art. 21(2)(d)", "capability": "seal"},
        {"requirement": "Security monitoring", "article": "Art. 21(2)(g)", "capability": "watch"},
        {"requirement": "Cryptographic controls", "article": "Art. 21(2)(h)", "capability": "crypto"},
        {"requirement": "Access control", "article": "Art. 21(2)(i)", "capability": "rbac"},
        {"requirement": "Audit trail", "article": "Art. 21(2)", "capability": "audit-log"},
        {"requirement": "Third-party cooperation", "article": "Art. 21(3)", "capability": "derived-list"},
    ],
    "dora": [
        {"requirement": "ICT asset management", "article": "Art. 8(1)", "capability": "scan"},
        {"requirement": "Data integrity testing", "article": "Art. 9(2)", "capability": "integrity"},
        {"requirement": "Continuous monitoring", "article": "Art. 9(2)", "capability": "watch"},
        {"requirement": "Incident detection", "article": "Art. 10(1)", "capability": "compare"},
        {"requirement": "Incident classification evidence", "article": "Art. 17", "capability": "report"},
        {"requirement": "Digital operational resilience testing", "article": "Art. 24", "capability": "verify-manifest"},
        {"requirement": "ICT third-party risk", "article": "Art. 28", "capability": "derived-list"},
        {"requirement": "Information sharing", "article": "Art. 45", "capability": "share"},
    ],
    "iso27001": [
        {"requirement": "Inventory of assets", "article": "A.8.1", "capability": "scan"},
        {"requirement": "Classification of information", "article": "A.8.2", "capability": "tag"},
        {"requirement": "Cryptographic controls", "article": "A.10.1", "capability": "crypto"},
        {"requirement": "Logging and monitoring", "article": "A.12.4", "capability": "audit-log"},
        {"requirement": "System integrity", "article": "A.14.1", "capability": "integrity"},
        {"requirement": "Secure development", "article": "A.14.2", "capability": "seal"},
        {"requirement": "Supplier relationships", "article": "A.15.1", "capability": "derived-list"},
        {"requirement": "Incident management", "article": "A.16.1", "capability": "compare"},
        {"requirement": "Evidence collection", "article": "A.16.1.7", "capability": "report"},
    ],
}

_CAPABILITY_MAP: dict[str, str] = {
    "scan": "census scan (cryptographic file inventory)",
    "integrity": "census integrity (tamper detection with differential mode)",
    "compare": "census compare (breach detection via batch_verify)",
    "report": "census report (HTML/PDF/ZIP forensic reports)",
    "seal": "census seal / verify-seal (HMAC-SHA256 tamper evidence)",
    "watch": "census watch (continuous filesystem monitoring)",
    "crypto": "AES-256-GCM metadata encryption, T0/T1/T2 proof chain",
    "rbac": "RBAC scoped API keys with audit trail",
    "audit-log": "census audit-log (tamper-evident JSONL hash chain)",
    "derived-list": "census derived-list (HMAC-SHA256 opaque third-party verification)",
    "share": "census share (time-limited, use-limited share tokens)",
    "verify-manifest": "census verify-manifest (full-chain registry verification)",
    "tag": "census tag (structured key-value classification with encryption)",
}


def _now_iso() -> str:
    return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")


def _assess_requirement(
    req: dict[str, str],
    total_files: int,
    attested_files: int,
    integrity: Optional[IntegrityReport],
) -> RequirementStatus:
    """Determine the status of a single requirement based on available data."""
    cap = req["capability"]
    status = "pass"
    detail = ""

    if cap == "scan":
        if total_files == 0:
            status = "fail"
            detail = "No files inventoried"
        elif attested_files < total_files:
            status = "warn"
            detail = f"{total_files - attested_files} files pending attestation"
        else:
            detail = f"{attested_files} files attested"

    elif cap == "integrity":
        if integrity is None:
            status = "warn"
            detail = "Integrity check not performed"
        elif integrity.has_discrepancies:
            status = "fail"
            detail = (
                f"{len(integrity.modified)} modified, "
                f"{len(integrity.missing)} missing, "
                f"{len(integrity.new_files)} new"
            )
        else:
            detail = f"{integrity.unchanged} files verified"

    elif cap in ("compare", "report", "seal", "watch", "crypto",
                 "rbac", "audit-log", "derived-list", "share",
                 "verify-manifest", "tag"):
        status = "pass"
        detail = "Capability available"

    return RequirementStatus(
        requirement=req["requirement"],
        framework=req.get("article", ""),
        article=req.get("article", ""),
        census_capability=_CAPABILITY_MAP.get(cap, cap),
        status=status,
        detail=detail,
    )


def generate_compliance_report(
    manifest: Manifest,
    *,
    template: str = "nis2",
    integrity: Optional[IntegrityReport] = None,
) -> ComplianceReport:
    """Build a compliance report from manifest data and optional integrity check.

    No API calls are made — this is 100% local.
    """
    if template not in TEMPLATES:
        raise ValueError(
            f"Unknown template: {template!r}. Valid: {', '.join(sorted(TEMPLATES))}"
        )

    entries = list(manifest.iter_entries())
    total = len(entries)
    attested = sum(1 for e in entries if e.attested)
    pending = total - attested

    requirements = []
    for req in _REQUIREMENTS[template]:
        rs = _assess_requirement(req, total, attested, integrity)
        requirements.append(rs)

    passed = sum(1 for r in requirements if r.status == "pass")
    warnings = sum(1 for r in requirements if r.status == "warn")
    failures = sum(1 for r in requirements if r.status == "fail")

    return ComplianceReport(
        template=template,
        generated_at=_now_iso(),
        census_version=__version__,
        manifest_path="",
        manifest_root=manifest.root_dir,
        total_files=total,
        attested_files=attested,
        pending_files=pending,
        integrity_ok=None if integrity is None else not integrity.has_discrepancies,
        integrity_modified=len(integrity.modified) if integrity else 0,
        integrity_missing=len(integrity.missing) if integrity else 0,
        integrity_new=len(integrity.new_files) if integrity else 0,
        requirements=requirements,
        passed=passed,
        warnings=warnings,
        failures=failures,
    )


_FRAMEWORK_NAMES: dict[str, str] = {
    "nis2": "NIS2 Directive (EU 2022/2555)",
    "dora": "DORA (EU 2022/2554)",
    "iso27001": "ISO/IEC 27001:2022",
}


def render_compliance_html(report: ComplianceReport) -> str:
    """Render a compliance report as self-contained HTML."""
    fw_name = html.escape(_FRAMEWORK_NAMES.get(report.template, report.template))
    generated = html.escape(report.generated_at)
    version = html.escape(report.census_version)
    root = html.escape(report.manifest_root)

    status_emoji = {"pass": "&#10003;", "warn": "&#9888;", "fail": "&#10007;"}
    status_class = {"pass": "ok", "warn": "warn-status", "fail": "fail-status"}

    req_rows = ""
    for r in report.requirements:
        cls = status_class.get(r.status, "")
        icon = status_emoji.get(r.status, "")
        req_rows += (
            f'<tr><td class="{html.escape(cls)}">{icon}</td>'
            f"<td>{html.escape(r.requirement)}</td>"
            f"<td><code>{html.escape(r.article)}</code></td>"
            f"<td>{html.escape(r.census_capability)}</td>"
            f"<td>{html.escape(r.detail)}</td></tr>\n"
        )

    integrity_section = ""
    if report.integrity_ok is not None:
        i_status = "OK" if report.integrity_ok else "VIOLATIONS DETECTED"
        i_class = "ok" if report.integrity_ok else "fail-status"
        integrity_section = f"""
<h2>Integrity Check</h2>
<p class="{html.escape(i_class)}"><strong>Result:</strong> {html.escape(i_status)}</p>
<p>Modified: {report.integrity_modified} | Missing: {report.integrity_missing} | New: {report.integrity_new}</p>
"""

    return f"""\
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>CertiSigma Census &mdash; {fw_name} Compliance Report</title>
<style>
:root {{ --bg: #fafbfc; --fg: #1a1a2e; --accent: #0f3460; --border: #dde; --ok: #27ae60; --warn: #e67e22; --fail: #e74c3c; }}
* {{ margin: 0; padding: 0; box-sizing: border-box; }}
body {{ font-family: "Segoe UI", system-ui, sans-serif; background: var(--bg); color: var(--fg); line-height: 1.6; padding: 2rem; max-width: 1100px; margin: auto; }}
h1 {{ color: var(--accent); border-bottom: 3px solid var(--accent); padding-bottom: .4rem; margin-bottom: 1.5rem; }}
h2 {{ color: var(--accent); margin: 1.5rem 0 .8rem; }}
table {{ width: 100%; border-collapse: collapse; margin: 1rem 0; font-size: .9rem; }}
th {{ background: var(--accent); color: #fff; padding: .5rem .7rem; text-align: left; }}
td {{ padding: .4rem .7rem; border-bottom: 1px solid var(--border); }}
tr:hover td {{ background: #eef; }}
code {{ font-family: monospace; font-size: .85em; background: #eef; padding: .1rem .3rem; border-radius: 3px; }}
.summary {{ display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 1rem; margin: 1rem 0; }}
.card {{ background: #fff; border: 1px solid var(--border); border-radius: 8px; padding: 1rem; text-align: center; }}
.card .label {{ font-size: .85rem; color: #666; }}
.card .value {{ font-size: 1.8rem; font-weight: 700; }}
.ok {{ color: var(--ok); font-weight: 600; }}
.warn-status {{ color: var(--warn); font-weight: 600; }}
.fail-status {{ color: var(--fail); font-weight: 600; }}
footer {{ margin-top: 3rem; border-top: 1px solid var(--border); padding-top: 1rem; font-size: .85rem; color: #888; }}
</style>
</head>
<body>

<h1>CertiSigma Census &mdash; {fw_name}</h1>

<p><strong>Generated:</strong> {generated}</p>
<p><strong>Tool version:</strong> certisigma-census {version}</p>
<p><strong>Manifest root:</strong> <code>{root}</code></p>

<h2>Summary</h2>
<div class="summary">
<div class="card"><div class="label">Total Files</div><div class="value">{report.total_files}</div></div>
<div class="card"><div class="label">Attested</div><div class="value ok">{report.attested_files}</div></div>
<div class="card"><div class="label">Pending</div><div class="value">{report.pending_files}</div></div>
<div class="card"><div class="label">Passed</div><div class="value ok">{report.passed}</div></div>
<div class="card"><div class="label">Warnings</div><div class="value warn-status">{report.warnings}</div></div>
<div class="card"><div class="label">Failures</div><div class="value fail-status">{report.failures}</div></div>
</div>

{integrity_section}

<h2>Requirements</h2>
<table>
<thead><tr><th></th><th>Requirement</th><th>Article</th><th>Census Capability</th><th>Detail</th></tr></thead>
<tbody>
{req_rows}
</tbody>
</table>

<footer>
<p>Generated by CertiSigma Census v{version} &mdash; <a href="https://certisigma.ch">certisigma.ch</a></p>
<p>Verify this report: <code>sha256sum report.html</code></p>
</footer>

</body>
</html>"""


def render_compliance_json(report: ComplianceReport) -> dict:
    """Convert a ComplianceReport to a serializable dict."""
    return asdict(report)
