"""Compliance presets for regulated industries.

Four named presets bundle enterprise safety features into a single flag:

- **DEVELOPMENT** — audit logging + WAL + AI content labels.
- **STANDARD** — adds HMAC audit chain, governed workflow, approval gates,
  execution fingerprint.
- **REGULATED** — adds signed WAL, data residency, SBOM generation,
  mandatory human review, evidence bundle export.
- **HIPAA** — PHI detection, file access controls, encryption at rest,
  HMAC audit chain, governed workflow, BAA-ready evidence bundle.

Each sub-feature can also be enabled independently outside of presets.

Usage::

    config = ComplianceConfig.from_preset(CompliancePreset.STANDARD)
    # Override individual features:
    config = dataclasses.replace(config, sbom_enabled=True)
"""

from __future__ import annotations

import dataclasses
import hashlib
import json
import logging
import re
import time
from dataclasses import dataclass
from enum import Enum
from typing import TYPE_CHECKING, Any

if TYPE_CHECKING:
    from pathlib import Path

logger = logging.getLogger(__name__)

_ISO_TIMESTAMP_FMT = "%Y-%m-%dT%H:%M:%SZ"
_MANIFEST_JSON = "manifest.json"


class CompliancePreset(Enum):
    """Named compliance tiers with escalating requirements."""

    DEVELOPMENT = "development"
    STANDARD = "standard"
    REGULATED = "regulated"
    HIPAA = "hipaa"


@dataclass
class ComplianceConfig:
    """Full compliance configuration derived from a preset or individual toggles.

    Attributes:
        preset: The named preset that produced this config (None if ad-hoc).
        audit_logging: Append-only JSONL audit log.
        audit_hmac_chain: HMAC-chained tamper-evident audit log.
        wal_enabled: Write-ahead log for crash-safe decisions.
        wal_signed: Ed25519/HMAC-signed WAL entries.
        ai_content_labels: Prepend AI-generated header to modified files.
        ai_label_format: Format string for the AI content label header.
        governed_workflow: Deterministic phase-based execution.
        approval_gates: Human sign-off at review phase.
        mandatory_human_review: Human review required for ALL tasks.
        execution_fingerprint: Compute and store determinism fingerprint.
        data_residency: Restrict model endpoints to a specific region.
        data_residency_region: Region identifier (e.g. "eu").
        sbom_enabled: Generate CycloneDX SBOM after each task.
        evidence_bundle: Auto-export execution evidence on run completion.
        hipaa_mode: Enable comprehensive HIPAA compliance mode.
        phi_detection: Enable PHI detection in agent inputs/outputs.
        phi_file_patterns: Additional glob patterns blocking PHI file access.
        encrypt_state_at_rest: Encrypt .sdd/ state files with AES-256-GCM.
        baa_contact: Name/email of the BAA signatory for compliance reports.
    """

    preset: CompliancePreset | None = None
    audit_logging: bool = False
    audit_hmac_chain: bool = False
    wal_enabled: bool = False
    wal_signed: bool = False
    ai_content_labels: bool = False
    ai_label_format: str = "// Generated by AI agent (Bernstein)"
    governed_workflow: bool = False
    approval_gates: bool = False
    mandatory_human_review: bool = False
    execution_fingerprint: bool = False
    data_residency: bool = False
    data_residency_region: str = ""
    sbom_enabled: bool = False
    evidence_bundle: bool = False
    # HIPAA-specific controls
    hipaa_mode: bool = False
    phi_detection: bool = False
    phi_file_patterns: list[str] = dataclasses.field(default_factory=list)
    encrypt_state_at_rest: bool = False
    baa_contact: str = ""

    @classmethod
    def from_preset(cls, preset: CompliancePreset) -> ComplianceConfig:
        """Build a full configuration from a named preset.

        Args:
            preset: The compliance tier to activate.

        Returns:
            ComplianceConfig with all features set to the preset's defaults.
        """
        if preset == CompliancePreset.DEVELOPMENT:
            return cls(
                preset=preset,
                audit_logging=True,
                wal_enabled=True,
                ai_content_labels=True,
            )

        if preset == CompliancePreset.STANDARD:
            return cls(
                preset=preset,
                audit_logging=True,
                audit_hmac_chain=True,
                wal_enabled=True,
                ai_content_labels=True,
                governed_workflow=True,
                approval_gates=True,
                execution_fingerprint=True,
            )

        if preset == CompliancePreset.HIPAA:
            return cls(
                preset=preset,
                audit_logging=True,
                audit_hmac_chain=True,
                wal_enabled=True,
                wal_signed=True,
                ai_content_labels=False,
                governed_workflow=True,
                approval_gates=True,
                mandatory_human_review=False,
                execution_fingerprint=True,
                data_residency=False,
                sbom_enabled=False,
                evidence_bundle=True,
                hipaa_mode=True,
                phi_detection=True,
                encrypt_state_at_rest=True,
            )

        # REGULATED
        return cls(
            preset=preset,
            audit_logging=True,
            audit_hmac_chain=True,
            wal_enabled=True,
            wal_signed=True,
            ai_content_labels=True,
            governed_workflow=True,
            approval_gates=True,
            mandatory_human_review=True,
            execution_fingerprint=True,
            data_residency=True,
            data_residency_region="eu",
            sbom_enabled=True,
            evidence_bundle=True,
        )

    @classmethod
    def from_dict(cls, data: dict[str, Any] | str) -> ComplianceConfig:
        """Parse compliance config from a YAML/dict mapping.

        Accepts either ``compliance: standard`` (string -> preset) or a
        mapping with individual feature toggles.

        Args:
            data: Mapping from the ``compliance`` section of bernstein.yaml.

        Returns:
            Fully resolved ComplianceConfig.
        """
        # If the value is just a preset name string, delegate to from_preset.
        if isinstance(data, str):
            return cls.from_preset(CompliancePreset(data.lower()))

        preset_raw = data.get("preset")
        base = cls.from_preset(CompliancePreset(str(preset_raw).lower())) if preset_raw is not None else cls()

        # Override individual fields from the mapping.
        overrides: dict[str, Any] = {}
        field_names = {f.name for f in dataclasses.fields(cls) if f.name != "preset"}
        for key, value in data.items():
            if key == "preset":
                continue
            if key in field_names:
                overrides[key] = value
        if overrides:
            base = dataclasses.replace(base, **overrides)
        return base

    def check_prerequisites(self) -> list[str]:
        """Check for missing prerequisites based on the active features.

        Returns:
            List of human-readable warnings (empty when everything is OK).
        """
        warnings: list[str] = []

        if self.audit_hmac_chain and not self.audit_logging:
            warnings.append("HMAC audit chain requires audit_logging to be enabled")

        if self.wal_signed and not self.wal_enabled:
            warnings.append("Signed WAL requires wal_enabled to be enabled")

        if self.governed_workflow and not self.wal_enabled:
            warnings.append("Governed workflow requires WAL for crash-safe decision history")

        if self.mandatory_human_review and not self.approval_gates:
            warnings.append("Mandatory human review requires approval_gates to be enabled")

        if self.data_residency and not self.data_residency_region:
            warnings.append("Data residency enabled but no region specified (set data_residency_region)")

        if self.evidence_bundle and not self.wal_enabled:
            warnings.append("Evidence bundle export requires WAL to be enabled")

        if self.evidence_bundle and not self.audit_logging:
            warnings.append("Evidence bundle export requires audit_logging to be enabled")

        if self.hipaa_mode and not self.phi_detection:
            warnings.append("HIPAA mode requires phi_detection to be enabled")

        if self.hipaa_mode and not self.audit_hmac_chain:
            warnings.append("HIPAA mode requires audit_hmac_chain for tamper-evident logging")

        if self.phi_detection and not self.hipaa_mode:
            warnings.append("phi_detection is intended for use with hipaa_mode")

        if self.encrypt_state_at_rest and not self.hipaa_mode:
            warnings.append("encrypt_state_at_rest is most useful when hipaa_mode is enabled")

        return warnings

    def to_dict(self) -> dict[str, Any]:
        """Serialize to a JSON-safe dict for persistence and API responses."""
        return {
            "preset": self.preset.value if self.preset else None,
            "audit_logging": self.audit_logging,
            "audit_hmac_chain": self.audit_hmac_chain,
            "wal_enabled": self.wal_enabled,
            "wal_signed": self.wal_signed,
            "ai_content_labels": self.ai_content_labels,
            "ai_label_format": self.ai_label_format,
            "governed_workflow": self.governed_workflow,
            "approval_gates": self.approval_gates,
            "mandatory_human_review": self.mandatory_human_review,
            "execution_fingerprint": self.execution_fingerprint,
            "data_residency": self.data_residency,
            "data_residency_region": self.data_residency_region,
            "sbom_enabled": self.sbom_enabled,
            "evidence_bundle": self.evidence_bundle,
            "hipaa_mode": self.hipaa_mode,
            "phi_detection": self.phi_detection,
            "phi_file_patterns": self.phi_file_patterns,
            "encrypt_state_at_rest": self.encrypt_state_at_rest,
            "baa_contact": self.baa_contact,
        }


# ---------------------------------------------------------------------------
# AI content labeling
# ---------------------------------------------------------------------------

# Comment prefix by file extension.
_COMMENT_STYLES: dict[str, str] = {
    ".py": "#",
    ".rb": "#",
    ".sh": "#",
    ".bash": "#",
    ".zsh": "#",
    ".yaml": "#",
    ".yml": "#",
    ".toml": "#",
    ".r": "#",
    ".pl": "#",
    ".pm": "#",
    ".js": "//",
    ".ts": "//",
    ".tsx": "//",
    ".jsx": "//",
    ".java": "//",
    ".c": "//",
    ".cpp": "//",
    ".h": "//",
    ".hpp": "//",
    ".cs": "//",
    ".go": "//",
    ".rs": "//",
    ".swift": "//",
    ".kt": "//",
    ".scala": "//",
    ".dart": "//",
    ".css": "/*",
    ".html": "<!--",
    ".xml": "<!--",
    ".sql": "--",
    ".lua": "--",
    ".hs": "--",
}

_COMMENT_CLOSE: dict[str, str] = {
    "/*": " */",
    "<!--": " -->",
}


def ai_label_for_file(filepath: Path, label_text: str) -> str | None:
    """Generate the AI content label line appropriate for a file's language.

    Args:
        filepath: Path to the file being labeled.
        label_text: The label message (e.g. "Generated by AI agent (Bernstein)").

    Returns:
        The formatted comment line, or None if the file type is unknown.
    """
    suffix = filepath.suffix.lower()
    prefix = _COMMENT_STYLES.get(suffix)
    if prefix is None:
        return None
    close = _COMMENT_CLOSE.get(prefix, "")
    return f"{prefix} {label_text}{close}"


# ---------------------------------------------------------------------------
# SBOM generation (CycloneDX stub)
# ---------------------------------------------------------------------------


@dataclass(frozen=True)
class SBOMEntry:
    """A single component in the Software Bill of Materials."""

    name: str
    version: str
    purl: str  # Package URL (e.g. "pkg:pypi/requests@2.31.0")


def generate_sbom(
    components: list[SBOMEntry],
    run_id: str,
    output_dir: Path,
) -> Path:
    """Generate a CycloneDX JSON SBOM and write it to disk.

    Args:
        components: List of software components to include.
        run_id: Orchestration run identifier.
        output_dir: Directory to write the SBOM file into.

    Returns:
        Path to the written SBOM file.
    """
    output_dir.mkdir(parents=True, exist_ok=True)
    sbom = {
        "bomFormat": "CycloneDX",
        "specVersion": "1.5",
        "serialNumber": f"urn:uuid:{run_id}",
        "version": 1,
        "metadata": {
            "timestamp": time.strftime(_ISO_TIMESTAMP_FMT, time.gmtime()),
            "tools": [{"vendor": "Bernstein", "name": "bernstein", "version": "1.0.0"}],
        },
        "components": [
            {
                "type": "library",
                "name": entry.name,
                "version": entry.version,
                "purl": entry.purl,
            }
            for entry in components
        ],
    }
    path = output_dir / f"sbom-{run_id}.cdx.json"
    path.write_text(json.dumps(sbom, indent=2))
    logger.info("SBOM written: %s (%d components)", path, len(components))
    return path


# ---------------------------------------------------------------------------
# Evidence bundle export
# ---------------------------------------------------------------------------


def export_evidence_bundle(
    run_id: str,
    sdd_dir: Path,
    output_dir: Path | None = None,
) -> Path:
    """Package compliance evidence into a single directory for export.

    Collects WAL, audit log, workflow events, SBOM, and execution
    fingerprint into ``<output_dir>/evidence-<run_id>/``.

    Args:
        run_id: Orchestration run identifier.
        sdd_dir: Path to the ``.sdd`` directory.
        output_dir: Where to write the bundle. Defaults to ``sdd_dir/evidence/``.

    Returns:
        Path to the evidence bundle directory.
    """
    import shutil

    if output_dir is None:
        output_dir = sdd_dir / "evidence"
    bundle_dir = output_dir / f"evidence-{run_id}"
    bundle_dir.mkdir(parents=True, exist_ok=True)

    # Collect artifacts
    artifacts: list[tuple[str, Path]] = [
        ("wal", sdd_dir / "runtime" / "wal"),
        ("audit", sdd_dir / "audit"),
        ("workflow", sdd_dir / "runtime" / "workflow"),
        ("sbom", sdd_dir / "sbom"),
    ]

    manifest_entries: list[dict[str, str]] = []
    for label, src in artifacts:
        if src.exists():
            dest = bundle_dir / label
            if src.is_dir():
                shutil.copytree(src, dest, dirs_exist_ok=True)
            else:
                shutil.copy2(src, dest)
            manifest_entries.append({"artifact": label, "source": str(src)})

    # Write bundle manifest
    manifest = {
        "run_id": run_id,
        "exported_at": time.strftime(_ISO_TIMESTAMP_FMT, time.gmtime()),
        "artifacts": manifest_entries,
    }
    (bundle_dir / _MANIFEST_JSON).write_text(json.dumps(manifest, indent=2))
    logger.info("Evidence bundle exported: %s (%d artifacts)", bundle_dir, len(manifest_entries))
    return bundle_dir


# ---------------------------------------------------------------------------
# SOC 2 evidence export
# ---------------------------------------------------------------------------


def parse_period(period: str) -> tuple[str, str]:
    """Parse a period string into ISO 8601 start/end timestamps.

    Supported formats:
      - ``Q1-2026`` through ``Q4-2026`` (quarter)
      - ``2026-03`` (month)
      - ``2026`` (full year)

    Args:
        period: Period string to parse.

    Returns:
        ``(start, end)`` as ISO 8601 date strings (``YYYY-MM-DD``).

    Raises:
        ValueError: If the period string cannot be parsed.
    """
    # Quarter: Q1-2026 .. Q4-2026
    m = re.match(r"^Q([1-4])-(\d{4})$", period, re.IGNORECASE)
    if m:
        quarter = int(m.group(1))
        year = int(m.group(2))
        start_month = (quarter - 1) * 3 + 1
        end_month = quarter * 3
        # Last day of the end month
        end_day = 30 if end_month in (4, 6, 9, 11) else 31
        return f"{year}-{start_month:02d}-01", f"{year}-{end_month:02d}-{end_day:02d}"

    # Month: 2026-03
    m = re.match(r"^(\d{4})-(\d{2})$", period)
    if m:
        year = int(m.group(1))
        month = int(m.group(2))
        if not 1 <= month <= 12:
            msg = f"Invalid month in period: {period!r}"
            raise ValueError(msg)
        # Last day of the month
        if month == 12:
            last_day = 31
        elif month in (4, 6, 9, 11):
            last_day = 30
        elif month == 2:
            last_day = 29 if (year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)) else 28
        else:
            last_day = 31
        return f"{year}-{month:02d}-01", f"{year}-{month:02d}-{last_day}"

    # Year: 2026
    m = re.match(r"^(\d{4})$", period)
    if m:
        year = m.group(1)
        return f"{year}-01-01", f"{year}-12-31"

    msg = f"Cannot parse period: {period!r}. Use Q1-2026, 2026-03, or 2026."
    raise ValueError(msg)


# ---------------------------------------------------------------------------
# SOC 2 Trust Services Criteria control mappings
# ---------------------------------------------------------------------------

# Maps SOC 2 control IDs to the artifact types that serve as evidence.
_SOC2_CONTROL_MAP: dict[str, dict[str, str]] = {
    "CC6.1": {
        "title": "Logical and Physical Access Controls",
        "description": "The entity implements logical access security software, "
        "infrastructure, and architectures over protected information assets.",
        "evidence_types": "audit_logs,compliance_config",
    },
    "CC6.8": {
        "title": "Controls Against Threats and Vulnerabilities",
        "description": "The entity implements controls to prevent or detect and act upon "
        "the introduction of unauthorized or malicious software.",
        "evidence_types": "sbom,compliance_config",
    },
    "CC7.2": {
        "title": "System Monitoring",
        "description": "The entity monitors system components and the operation of those "
        "components for anomalies that are indicative of malicious acts.",
        "evidence_types": "audit_logs,wal",
    },
    "CC7.3": {
        "title": "Detection and Response",
        "description": "The entity evaluates anomalies and incidents to determine whether "
        "they constitute security incidents.",
        "evidence_types": "audit_logs,wal",
    },
    "CC8.1": {
        "title": "Change Management",
        "description": "The entity authorizes, designs, develops, configures, documents, "
        "tests, approves, and implements changes to infrastructure and software.",
        "evidence_types": "audit_logs,wal,sbom",
    },
}


def _build_soc2_control_mappings(
    artifacts: list[dict[str, Any]],
    verification: dict[str, Any],
) -> list[dict[str, Any]]:
    """Build SOC 2 control-to-evidence mappings.

    For each SOC 2 control, lists which artifacts in the package satisfy it
    and whether the evidence is present and verified.

    Args:
        artifacts: Collected artifact metadata from the export.
        verification: HMAC/Merkle verification results.

    Returns:
        List of control mapping dicts with status and evidence references.
    """
    artifact_types = {a["type"] for a in artifacts}
    hmac_valid = verification.get("hmac_chain", {}).get("valid", False) if verification.get("hmac_chain") else False

    mappings: list[dict[str, Any]] = []
    for control_id, meta in _SOC2_CONTROL_MAP.items():
        required_types = set(meta["evidence_types"].split(","))
        present = required_types & artifact_types
        missing = required_types - artifact_types
        satisfied = len(missing) == 0

        mappings.append(
            {
                "control_id": control_id,
                "title": meta["title"],
                "description": meta["description"],
                "satisfied": satisfied,
                "evidence_present": sorted(present),
                "evidence_missing": sorted(missing),
                "integrity_verified": hmac_valid,
            }
        )

    return mappings


def _build_merkle_attestation(merkle_dir: Path | None) -> dict[str, Any] | None:
    """Build a Merkle root attestation summary from seal files.

    Reads all seal JSON files and produces an attestation record with the
    latest root hash suitable for anchoring or external verification.

    Args:
        merkle_dir: Directory containing Merkle seal JSON files, or None.

    Returns:
        Attestation dict, or None if no seals exist.
    """
    if merkle_dir is None or not merkle_dir.is_dir():
        return None

    seals: list[dict[str, Any]] = []
    for seal_file in sorted(merkle_dir.glob("*.json")):
        try:
            seal_data = json.loads(seal_file.read_text())
            seals.append(
                {
                    "file": seal_file.name,
                    "root_hash": seal_data.get("root_hash", ""),
                    "sealed_at": seal_data.get("sealed_at_iso", ""),
                    "leaf_count": seal_data.get("leaf_count", 0),
                    "algorithm": seal_data.get("algorithm", "sha256"),
                }
            )
        except (json.JSONDecodeError, OSError):
            continue

    if not seals:
        return None

    latest = seals[-1]
    return {
        "attestation_type": "merkle_root",
        "latest_root_hash": latest["root_hash"],
        "latest_sealed_at": latest["sealed_at"],
        "total_seals": len(seals),
        "seals": seals,
        "generated_at": time.strftime(_ISO_TIMESTAMP_FMT, time.gmtime()),
    }


def _build_evidence_summary(
    period: str,
    start_date: str,
    end_date: str,
    artifacts: list[dict[str, Any]],
    verification: dict[str, Any],
    control_mappings: list[dict[str, Any]],
    merkle_attestation: dict[str, Any] | None,
) -> str:
    """Generate a PDF-ready Markdown evidence summary for auditors.

    Args:
        period: Original period string.
        start_date: ISO start date.
        end_date: ISO end date.
        artifacts: Collected artifact metadata.
        verification: HMAC/Merkle verification results.
        control_mappings: SOC 2 control mapping results.
        merkle_attestation: Merkle attestation data or None.

    Returns:
        Markdown string suitable for conversion to PDF.
    """
    lines: list[str] = []
    generated = time.strftime(_ISO_TIMESTAMP_FMT, time.gmtime())

    lines.append(f"# SOC 2 Evidence Package — {period}")
    lines.append("")
    lines.append(f"**Period**: {start_date} to {end_date}")
    lines.append(f"**Generated**: {generated}")
    lines.append("**Package type**: SOC 2 Type II Evidence Bundle")
    lines.append("")

    # Executive summary
    lines.append("## Executive Summary")
    lines.append("")
    total_controls = len(control_mappings)
    satisfied = sum(1 for m in control_mappings if m["satisfied"])
    hmac_chain = verification.get("hmac_chain")
    hmac_status = "PASSED" if hmac_chain and hmac_chain.get("valid") else "NOT VERIFIED"
    lines.append(
        f"This package contains compliance evidence for the period "
        f"{start_date} to {end_date}. "
        f"**{satisfied}/{total_controls}** SOC 2 controls have supporting evidence. "
        f"HMAC chain integrity: **{hmac_status}**."
    )
    lines.append("")

    # Artifacts inventory
    lines.append("## Evidence Artifacts")
    lines.append("")
    lines.append("| Artifact | Description | Files |")
    lines.append("|----------|-------------|-------|")
    for a in artifacts:
        lines.append(f"| {a['type']} | {a['description']} | {a.get('file_count', 'N/A')} |")
    if not artifacts:
        lines.append("| *(none collected)* | | |")
    lines.append("")

    # Control mappings
    lines.append("## SOC 2 Control Mappings")
    lines.append("")
    lines.append("| Control | Title | Status | Evidence Present | Missing |")
    lines.append("|---------|-------|--------|------------------|---------|")
    for m in control_mappings:
        status = "Satisfied" if m["satisfied"] else "Gap"
        present = ", ".join(m["evidence_present"]) or "—"
        missing = ", ".join(m["evidence_missing"]) or "—"
        lines.append(f"| {m['control_id']} | {m['title']} | {status} | {present} | {missing} |")
    lines.append("")

    # Integrity verification
    lines.append("## Integrity Verification")
    lines.append("")
    hmac_info = verification.get("hmac_chain")
    if hmac_info:
        valid_str = "PASSED" if hmac_info.get("valid") else "FAILED"
        lines.append(f"**HMAC Chain**: {valid_str}")
        if hmac_info.get("errors"):
            lines.append("")
            for err in hmac_info["errors"]:
                lines.append(f"- {err}")
        verified_at = hmac_info.get("verified_at", "")
        if verified_at:
            lines.append("")
            lines.append(f"Verified at: {verified_at}")
    else:
        lines.append("HMAC chain verification: *not performed*")
    lines.append("")

    # Merkle attestation
    lines.append("## Merkle Root Attestation")
    lines.append("")
    if merkle_attestation:
        lines.append(f"**Latest root hash**: `{merkle_attestation['latest_root_hash']}`")
        lines.append(f"**Sealed at**: {merkle_attestation['latest_sealed_at']}")
        lines.append(f"**Total seals**: {merkle_attestation['total_seals']}")
        lines.append("")
        lines.append("| Seal File | Root Hash | Sealed At |")
        lines.append("|-----------|-----------|-----------|")
        for s in merkle_attestation["seals"]:
            root_short = s["root_hash"][:16] + "..." if len(s["root_hash"]) > 16 else s["root_hash"]
            lines.append(f"| {s['file']} | `{root_short}` | {s['sealed_at']} |")
    else:
        lines.append("*No Merkle seals found for this period.*")
    lines.append("")

    # Footer
    lines.append("---")
    lines.append(f"*Generated by Bernstein Compliance Engine at {generated}*")
    lines.append("")

    return "\n".join(lines)


def export_soc2_package(
    sdd_dir: Path,
    period: str,
    output_path: Path | None = None,
    fmt: str = "zip",
) -> Path:
    """Export a SOC 2 evidence package for the given period.

    Collects audit logs, access records, compliance configuration, policy
    versions, HMAC chain verification results, Merkle seals, and SBOM files
    filtered to the requested period.  Produces either a directory or a zip
    archive suitable for delivery to auditors.

    Args:
        sdd_dir: Path to the ``.sdd`` directory.
        period: Period string (e.g. ``Q1-2026``, ``2026-03``, ``2026``).
        output_path: Where to write the package.  Defaults to
            ``sdd_dir / evidence / soc2-<period>``.
        fmt: Output format — ``"zip"`` or ``"dir"``.

    Returns:
        Path to the exported zip file or directory.

    Raises:
        ValueError: If the period cannot be parsed or no audit data exists.
    """
    import shutil

    start_date, end_date = parse_period(period)

    if output_path is None:
        output_path = sdd_dir / "evidence"
    output_path.mkdir(parents=True, exist_ok=True)

    bundle_name = f"soc2-{period}"
    bundle_dir = output_path / bundle_name
    if bundle_dir.exists():
        shutil.rmtree(bundle_dir)
    bundle_dir.mkdir(parents=True)

    artifacts_collected: list[dict[str, Any]] = []

    # --- 1. Audit logs (filtered by period) --------------------------------
    audit_dir = sdd_dir / "audit"
    if audit_dir.is_dir():
        dest = bundle_dir / "audit_logs"
        dest.mkdir()
        for jsonl_file in sorted(audit_dir.glob("*.jsonl")):
            # File names are YYYY-MM-DD.jsonl — filter by date range
            file_date = jsonl_file.stem  # e.g. "2026-01-15"
            if start_date <= file_date <= end_date:
                shutil.copy2(jsonl_file, dest / jsonl_file.name)
        copied = list(dest.iterdir())
        if copied:
            artifacts_collected.append(
                {
                    "type": "audit_logs",
                    "description": "HMAC-chained audit event logs",
                    "file_count": len(copied),
                    "period_filter": f"{start_date} to {end_date}",
                }
            )

    # --- 2. HMAC chain verification ----------------------------------------
    verification: dict[str, Any] = {"hmac_chain": None, "merkle": None}
    if audit_dir.is_dir():
        from bernstein.core.audit import AuditLog

        try:
            audit_log = AuditLog(audit_dir)
            valid, errors = audit_log.verify()
            verification["hmac_chain"] = {
                "valid": valid,
                "errors": errors,
                "verified_at": time.strftime(_ISO_TIMESTAMP_FMT, time.gmtime()),
            }
        except Exception as exc:
            verification["hmac_chain"] = {"valid": False, "errors": [str(exc)]}

    # --- 3. Merkle seals ---------------------------------------------------
    merkle_dir = audit_dir / "merkle" if audit_dir.is_dir() else sdd_dir / "audit" / "merkle"
    if merkle_dir.is_dir():
        dest = bundle_dir / "merkle_seals"
        dest.mkdir()
        for seal_file in sorted(merkle_dir.glob("*.json")):
            shutil.copy2(seal_file, dest / seal_file.name)
        copied = list(dest.iterdir())
        if copied:
            artifacts_collected.append(
                {
                    "type": "merkle_seals",
                    "description": "Merkle tree integrity seals",
                    "file_count": len(copied),
                }
            )

    # --- 4. Compliance configuration ---------------------------------------
    config_dir = sdd_dir / "config"
    if config_dir.is_dir():
        dest = bundle_dir / "compliance_config"
        dest.mkdir()
        for cfg_file in config_dir.iterdir():
            if cfg_file.is_file() and cfg_file.name != "audit-key":
                shutil.copy2(cfg_file, dest / cfg_file.name)
        copied = list(dest.iterdir())
        if copied:
            artifacts_collected.append(
                {
                    "type": "compliance_config",
                    "description": "Compliance and policy configuration",
                    "file_count": len(copied),
                }
            )

    # --- 5. WAL (write-ahead log) ------------------------------------------
    wal_dir = sdd_dir / "runtime" / "wal"
    if wal_dir.is_dir():
        dest = bundle_dir / "wal"
        dest.mkdir()
        for wal_file in sorted(wal_dir.glob("*")):
            if wal_file.is_file():
                shutil.copy2(wal_file, dest / wal_file.name)
        copied = list(dest.iterdir())
        if copied:
            artifacts_collected.append(
                {
                    "type": "wal",
                    "description": "Write-ahead log entries",
                    "file_count": len(copied),
                }
            )

    # --- 6. SBOM -----------------------------------------------------------
    sbom_dir = sdd_dir / "sbom"
    if sbom_dir.is_dir():
        dest = bundle_dir / "sbom"
        dest.mkdir()
        for sbom_file in sorted(sbom_dir.glob("*.json")):
            shutil.copy2(sbom_file, dest / sbom_file.name)
        copied = list(dest.iterdir())
        if copied:
            artifacts_collected.append(
                {
                    "type": "sbom",
                    "description": "Software Bill of Materials (CycloneDX)",
                    "file_count": len(copied),
                }
            )

    # --- 7. SOC 2 control mappings ------------------------------------------
    control_mappings = _build_soc2_control_mappings(artifacts_collected, verification)
    (bundle_dir / "control_mappings.json").write_text(json.dumps(control_mappings, indent=2))

    # --- 8. Merkle root attestation ----------------------------------------
    merkle_attestation = _build_merkle_attestation(merkle_dir if merkle_dir.is_dir() else None)
    if merkle_attestation:
        (bundle_dir / "merkle_attestation.json").write_text(json.dumps(merkle_attestation, indent=2))

    # --- 9. Evidence summary (PDF-ready Markdown) --------------------------
    evidence_summary = _build_evidence_summary(
        period,
        start_date,
        end_date,
        artifacts_collected,
        verification,
        control_mappings,
        merkle_attestation,
    )
    (bundle_dir / "evidence_summary.md").write_text(evidence_summary)

    # --- 10. Write verification results ------------------------------------
    (bundle_dir / "verification.json").write_text(json.dumps(verification, indent=2))

    # --- 11. Compute package checksum --------------------------------------
    file_checksums: dict[str, str] = {}
    for path in sorted(bundle_dir.rglob("*")):
        if path.is_file() and path.name != _MANIFEST_JSON:
            digest = hashlib.sha256(path.read_bytes()).hexdigest()
            file_checksums[str(path.relative_to(bundle_dir))] = digest

    # --- 12. Write manifest ------------------------------------------------
    manifest = {
        "package_type": "soc2-evidence",
        "period": period,
        "period_start": start_date,
        "period_end": end_date,
        "exported_at": time.strftime(_ISO_TIMESTAMP_FMT, time.gmtime()),
        "artifacts": artifacts_collected,
        "verification": verification,
        "control_mappings": control_mappings,
        "merkle_attestation": merkle_attestation,
        "file_checksums": file_checksums,
    }
    (bundle_dir / _MANIFEST_JSON).write_text(json.dumps(manifest, indent=2))

    # --- 10. Optionally zip ------------------------------------------------
    if fmt == "zip":
        zip_path = output_path / f"{bundle_name}.zip"
        shutil.make_archive(str(output_path / bundle_name), "zip", output_path, bundle_name)
        shutil.rmtree(bundle_dir)
        logger.info("SOC 2 evidence package exported: %s", zip_path)
        return zip_path

    logger.info("SOC 2 evidence package exported: %s", bundle_dir)
    return bundle_dir


# ---------------------------------------------------------------------------
# Compliance config persistence
# ---------------------------------------------------------------------------


def persist_compliance_config(config: ComplianceConfig, sdd_dir: Path) -> None:
    """Write the active compliance config to .sdd/config/compliance.json.

    Args:
        config: The compliance configuration to persist.
        sdd_dir: Path to the .sdd directory.
    """
    config_dir = sdd_dir / "config"
    config_dir.mkdir(parents=True, exist_ok=True)
    path = config_dir / "compliance.json"
    path.write_text(json.dumps(config.to_dict(), indent=2))
    logger.info("Compliance config persisted: %s (preset=%s)", path, config.preset)


def load_compliance_config(sdd_dir: Path) -> ComplianceConfig | None:
    """Load compliance config from .sdd/config/compliance.json if it exists.

    Args:
        sdd_dir: Path to the .sdd directory.

    Returns:
        ComplianceConfig if found, None otherwise.
    """
    path = sdd_dir / "config" / "compliance.json"
    if not path.exists():
        return None
    try:
        data = json.loads(path.read_text())
        return ComplianceConfig.from_dict(data)
    except (ValueError, KeyError):
        logger.warning("Failed to load compliance config from %s", path)
        return None
