"""ComplianceExporter — enriches audit events with NIST 800-53 control mappings.

Outputs events in a structured format with compliance annotations, suitable for
feeding into SIEM tools, compliance dashboards, and ATO evidence packages.

The output format follows OCSF (Open Cybersecurity Schema Framework) conventions
where practical, with NIST 800-53 control IDs attached to each event.

Example:
    from pyauthz.audit.compliance import ComplianceExporter
    from pyauthz.audit.logger import AuditLogger

    exporter = ComplianceExporter(path="logs/compliance.jsonl")
    audit = AuditLogger(exporters=[exporter])
    rp.set_audit_logger(audit)
"""

from __future__ import annotations

import json
from pathlib import Path
from typing import Any

from pyauthz.audit.models import AuditEvent, MutationAction, MutationEvent

# NIST 800-53 Rev 5 control mappings for authorization events
_DECISION_CONTROLS = {
    "ALLOW": ["AC-3", "AC-24", "AU-2"],
    "DENY": ["AC-3", "AC-6", "AC-24", "AU-2"],
}

# NIST 800-53 Rev 5 control mappings for mutation events
_MUTATION_CONTROLS: dict[str, list[str]] = {
    MutationAction.ROLE_ADDED: ["AC-2", "CM-3", "AU-2"],
    MutationAction.ROLE_REMOVED: ["AC-2", "CM-3", "AU-2"],
    MutationAction.ROLES_REPLACED: ["AC-2", "CM-3", "AU-2"],
    MutationAction.POLICY_ADDED: ["AC-3", "CM-3", "AU-2"],
    MutationAction.POLICY_REMOVED: ["AC-3", "CM-3", "AU-2"],
    MutationAction.POLICIES_REPLACED: ["AC-3", "CM-3", "AU-2"],
    MutationAction.TEMPORAL_ROLE_ASSIGNED: ["AC-2", "AU-2"],
    MutationAction.TEMPORAL_ROLE_REVOKED: ["AC-2", "AU-2"],
    MutationAction.DELEGATION_CREATED: ["AC-2", "AC-5", "AU-2"],
    MutationAction.DELEGATION_REVOKED: ["AC-2", "AC-5", "AU-2"],
}


def get_decision_controls(decision: str) -> list[str]:
    """Get NIST 800-53 controls applicable to a decision type."""
    return _DECISION_CONTROLS.get(decision, ["AU-2"])


def get_mutation_controls(action: MutationAction | str) -> list[str]:
    """Get NIST 800-53 controls applicable to a mutation action."""
    return _MUTATION_CONTROLS.get(str(action), ["AU-2"])


class ComplianceExporter:
    """Exports audit events enriched with NIST 800-53 control annotations.

    Each output line is a JSON object containing the original event data
    plus a ``nist_controls`` field listing applicable NIST 800-53 controls,
    and an ``event_category`` field ("authorization" or "configuration_change").

    This format is designed for consumption by SIEM tools (Splunk, ELK, etc.)
    and for generating ATO compliance evidence.

    Example:
        exporter = ComplianceExporter(path="logs/compliance.jsonl")
    """

    def __init__(self, path: str, *, service_id: str | None = None) -> None:
        self._path = Path(path)
        self._path.parent.mkdir(parents=True, exist_ok=True)
        self._service_id = service_id

    def export(self, event: AuditEvent | MutationEvent) -> None:
        """Export an event with compliance annotations."""
        if isinstance(event, AuditEvent):
            record = self._format_decision(event)
        else:
            record = self._format_mutation(event)

        with open(self._path, "a") as f:
            f.write(json.dumps(record, default=str) + "\n")

    def _format_decision(self, event: AuditEvent) -> dict[str, Any]:
        """Format an authorization decision with OCSF-inspired structure."""
        data = event.model_dump()
        data["event_category"] = "authorization"
        data["nist_controls"] = get_decision_controls(event.decision)
        if self._service_id and not data.get("service_id"):
            data["service_id"] = self._service_id
        return data

    def _format_mutation(self, event: MutationEvent) -> dict[str, Any]:
        """Format a configuration change with compliance annotations."""
        data = event.model_dump()
        data["event_category"] = "configuration_change"
        data["nist_controls"] = get_mutation_controls(event.mutation_action)
        if self._service_id and not data.get("service_id"):
            data["service_id"] = self._service_id
        return data
