# Familiar - Self-Hosted AI Companion Platform
# Copyright (c) 2026 George Scott Foley
# ORCID: 0009-0006-4957-0540
# Email: Georgescottfoley@proton.me
# Licensed under the MIT License - see LICENSE file for details

"""
Familiar Compliance Configuration Module

Provides pre-configured security settings for regulated environments:
- HIPAA (Healthcare)
- SOC2 (Enterprise)
- PCI-DSS (Payment Card)
- Custom compliance profiles

Usage:
    from familiar.core.compliance import ComplianceMode, apply_compliance_profile

    # Apply HIPAA settings
    config = apply_compliance_profile(base_config, ComplianceMode.HIPAA)

    # Or load from config file
    # compliance:
    #   mode: hipaa
"""

import json
import logging
import os
from dataclasses import dataclass, field
from enum import Enum
from pathlib import Path
from typing import Any, Dict, List, Optional, Tuple

logger = logging.getLogger(__name__)


class ComplianceMode(str, Enum):
    """Pre-defined compliance profiles."""

    NONE = "none"  # No special compliance requirements
    HIPAA = "hipaa"  # Healthcare - PHI protection
    SOC2 = "soc2"  # Enterprise - security controls
    PCI_DSS = "pci_dss"  # Payment card data
    GDPR = "gdpr"  # EU data protection
    CUSTOM = "custom"  # User-defined settings


@dataclass
class ComplianceConfig:
    """
    Compliance-related configuration settings.

    These settings are applied on top of base security configuration
    when a compliance mode is enabled.
    """

    # General
    mode: ComplianceMode = ComplianceMode.NONE

    # Data Protection
    encryption_required: bool = False
    encrypt_sessions: bool = False
    encrypt_memory: bool = False
    encrypt_history: bool = False
    pii_redaction_in_logs: bool = True

    # Audit & Logging
    audit_logging_required: bool = False
    log_retention_days: int = 90
    log_phi_access: bool = False
    immutable_audit_log: bool = False

    # Access Control
    session_timeout_minutes: int = 60
    require_reauth_for_sensitive: bool = False
    max_trust_level: str = "owner"  # Cap trust level for all users

    # Network
    require_tls: bool = True
    allowed_llm_providers: List[str] = field(default_factory=list)
    local_llm_required: bool = False

    # Data Handling
    data_retention_days: Optional[int] = None
    require_data_classification: bool = False
    phi_indicators: List[str] = field(default_factory=list)

    # Operational
    require_mfa_for_admin: bool = False
    auto_logout_on_idle: bool = False


# Pre-defined compliance profiles
COMPLIANCE_PROFILES: Dict[ComplianceMode, ComplianceConfig] = {
    ComplianceMode.NONE: ComplianceConfig(
        mode=ComplianceMode.NONE,
    ),
    ComplianceMode.HIPAA: ComplianceConfig(
        mode=ComplianceMode.HIPAA,
        # Data Protection
        encryption_required=True,
        encrypt_sessions=True,
        encrypt_memory=True,
        encrypt_history=True,
        pii_redaction_in_logs=False,  # Keep PHI in audit logs for compliance
        # Audit
        audit_logging_required=True,
        log_retention_days=2190,  # 6 years
        log_phi_access=True,
        immutable_audit_log=True,
        # Access Control
        session_timeout_minutes=15,
        require_reauth_for_sensitive=True,
        # Network
        require_tls=True,
        local_llm_required=False,  # Cloud OK with BAA
        # Data
        data_retention_days=2190,  # 6 years
        phi_indicators=[
            "patient",
            "diagnosis",
            "medication",
            "treatment",
            "medical",
            "health",
            "hospital",
            "doctor",
            "nurse",
            "prescription",
            "symptom",
            "condition",
            "surgery",
            "ssn",
            "social security",
            "insurance",
            "claim",
            "dob",
            "date of birth",
            "mrn",
            "medical record",
        ],
    ),
    ComplianceMode.SOC2: ComplianceConfig(
        mode=ComplianceMode.SOC2,
        # Data Protection
        encryption_required=True,
        encrypt_sessions=True,
        encrypt_memory=True,
        encrypt_history=True,
        pii_redaction_in_logs=True,
        # Audit
        audit_logging_required=True,
        log_retention_days=365,  # 1 year minimum
        immutable_audit_log=True,
        # Access Control
        session_timeout_minutes=30,
        require_reauth_for_sensitive=True,
        require_mfa_for_admin=True,
        # Network
        require_tls=True,
    ),
    ComplianceMode.PCI_DSS: ComplianceConfig(
        mode=ComplianceMode.PCI_DSS,
        # Data Protection
        encryption_required=True,
        encrypt_sessions=True,
        encrypt_memory=True,
        encrypt_history=True,
        pii_redaction_in_logs=True,
        # Audit
        audit_logging_required=True,
        log_retention_days=365,
        immutable_audit_log=True,
        # Access Control
        session_timeout_minutes=15,
        require_reauth_for_sensitive=True,
        require_mfa_for_admin=True,
        max_trust_level="trusted",  # Never allow full OWNER for PCI systems
        # Network
        require_tls=True,
    ),
    ComplianceMode.GDPR: ComplianceConfig(
        mode=ComplianceMode.GDPR,
        # Data Protection
        encryption_required=True,
        encrypt_sessions=True,
        encrypt_memory=True,
        encrypt_history=True,
        pii_redaction_in_logs=True,
        # Audit
        audit_logging_required=True,
        log_retention_days=365,
        # Access Control
        session_timeout_minutes=60,
        # Data - GDPR emphasizes data minimization
        data_retention_days=365,  # Shorter retention
        require_data_classification=True,
    ),
}


def get_compliance_config(mode: ComplianceMode) -> ComplianceConfig:
    """
    Get the compliance configuration for a given mode.

    Args:
        mode: Compliance mode to get config for

    Returns:
        ComplianceConfig for the specified mode
    """
    return COMPLIANCE_PROFILES.get(mode, COMPLIANCE_PROFILES[ComplianceMode.NONE])


def apply_compliance_profile(base_config: Dict[str, Any], mode: ComplianceMode) -> Dict[str, Any]:
    """
    Apply compliance profile settings to a base configuration.

    This merges compliance-required settings into the existing config,
    overriding values where the compliance profile is more restrictive.

    Args:
        base_config: Existing configuration dictionary
        mode: Compliance mode to apply

    Returns:
        Updated configuration dictionary
    """
    if mode == ComplianceMode.NONE:
        return base_config

    profile = get_compliance_config(mode)
    config = base_config.copy()

    # Ensure nested dicts exist
    config.setdefault("security", {})
    config.setdefault("observability", {})
    config.setdefault("agent", {})
    config.setdefault("compliance", {})

    # Apply compliance settings
    config["compliance"]["mode"] = mode.value
    config["compliance"]["profile"] = profile.__dict__

    # Security settings
    if profile.encryption_required:
        config["security"]["encrypt_sessions"] = profile.encrypt_sessions
        config["security"]["encrypt_memory"] = profile.encrypt_memory
        config["security"]["encrypt_history"] = profile.encrypt_history

    # Force encryption enabled when profile requires it
    config.setdefault("encryption", {})
    if profile.encryption_required:
        config["encryption"]["enabled"] = True

    if profile.session_timeout_minutes:
        config["security"]["session_timeout_minutes"] = min(
            config["security"].get("session_timeout_minutes", 9999), profile.session_timeout_minutes
        )

    if profile.require_reauth_for_sensitive:
        config["security"]["require_reauth_for_sensitive"] = True

    # Observability settings
    if profile.audit_logging_required:
        config["observability"]["audit_logging"] = True

    config["observability"]["log_retention_days"] = max(
        config["observability"].get("log_retention_days", 0), profile.log_retention_days
    )

    config["observability"]["pii_redaction"] = profile.pii_redaction_in_logs

    if profile.log_phi_access:
        config["observability"]["log_phi_access"] = True

    # Agent settings
    if profile.max_trust_level:
        config["agent"]["max_trust_level"] = profile.max_trust_level

    # LLM settings
    if profile.local_llm_required:
        config.setdefault("llm", {})
        config["llm"]["require_local"] = True

    if profile.allowed_llm_providers:
        config.setdefault("llm", {})
        config["llm"]["allowed_providers"] = profile.allowed_llm_providers

    logger.info(f"Applied compliance profile: {mode.value}")

    return config


def validate_compliance(config: Dict[str, Any], mode: ComplianceMode) -> List[str]:
    """
    Validate that a configuration meets compliance requirements.

    Args:
        config: Configuration to validate
        mode: Compliance mode to validate against

    Returns:
        List of validation errors (empty if valid)
    """
    if mode == ComplianceMode.NONE:
        return []

    profile = get_compliance_config(mode)
    errors = []

    security = config.get("security", {})
    observability = config.get("observability", {})

    # Check encryption
    if profile.encryption_required:
        if not security.get("encrypt_sessions"):
            errors.append(f"{mode.value}: Session encryption required but not enabled")
        if not security.get("encrypt_memory"):
            errors.append(f"{mode.value}: Memory encryption required but not enabled")
        if not security.get("encrypt_history"):
            errors.append(f"{mode.value}: History encryption required but not enabled")

        # Check if encryption key is configured
        key_env = security.get("encryption_key_env", "FAMILIAR_ENCRYPTION_KEY")
        if not os.environ.get(key_env):
            errors.append(f"{mode.value}: Encryption key not set ({key_env})")

    # Check audit logging
    if profile.audit_logging_required:
        if not observability.get("audit_logging"):
            errors.append(f"{mode.value}: Audit logging required but not enabled")

    # Check log retention
    if profile.log_retention_days:
        actual_retention = observability.get("log_retention_days", 0)
        if actual_retention < profile.log_retention_days:
            errors.append(
                f"{mode.value}: Log retention {actual_retention} days < required {profile.log_retention_days} days"
            )

    # Check session timeout
    if profile.session_timeout_minutes:
        actual_timeout = security.get("session_timeout_minutes", 9999)
        if actual_timeout > profile.session_timeout_minutes:
            errors.append(
                f"{mode.value}: Session timeout {actual_timeout}min > max allowed {profile.session_timeout_minutes}min"
            )

    return errors


def check_phi_content(text: str, mode: ComplianceMode) -> bool:
    """
    Check if text potentially contains PHI indicators.

    Args:
        text: Text to check
        mode: Compliance mode (determines which indicators to check)

    Returns:
        True if PHI indicators found
    """
    if mode != ComplianceMode.HIPAA:
        return False

    profile = get_compliance_config(mode)
    text_lower = text.lower()

    import re

    for indicator in profile.phi_indicators:
        # Use word-boundary matching to avoid false positives
        # (e.g., "date" matching "update", "condition" matching "air conditioning")
        pattern = r"\b" + re.escape(indicator.lower()) + r"\b"
        if re.search(pattern, text_lower):
            return True

    return False


class ComplianceAuditLogger:
    """
    Specialized audit logger for compliance events.

    Logs security-relevant events with the detail level required
    for compliance audits.

    Features:
    - Cryptographic hash chaining for tamper detection (HIPAA requirement)
    - Append-only log structure
    - Verification of log integrity
    """

    def __init__(self, config: ComplianceConfig, base_path: Path, encrypted_storage=None):
        self.config = config
        self.audit_log_path = base_path / "audit.log"
        self._last_hash: Optional[str] = None
        self._encrypted_storage = encrypted_storage
        self._ensure_audit_log()
        self._load_last_hash()

    def _ensure_audit_log(self) -> None:
        """Ensure audit log file exists with proper permissions."""
        if not self.audit_log_path.exists():
            self.audit_log_path.touch()
            self.audit_log_path.chmod(0o600)

    def _decrypt_line(self, line: str) -> Optional[str]:
        """Attempt to decrypt a log line; return plaintext JSON or None."""
        line = line.strip()
        if not line:
            return None
        # Try parsing as plaintext JSON first
        try:
            json.loads(line)
            return line
        except (json.JSONDecodeError, ValueError) as e:
            logger.debug("JSON parse error suppressed: %s", e)
        # Try decrypting
        if self._encrypted_storage and hasattr(self._encrypted_storage, "decrypt"):
            try:
                return self._encrypted_storage.decrypt(line)
            except Exception as e:
                logger.debug("Error suppressed: %s", e)
        return None

    def _load_last_hash(self) -> None:
        """Load the hash of the last log entry for chain continuity."""
        if not self.audit_log_path.exists():
            self._last_hash = "GENESIS"
            return

        try:
            with open(self.audit_log_path, "r") as f:
                lines = f.readlines()
                if lines:
                    last_line = lines[-1].strip()
                    if last_line:
                        decrypted = self._decrypt_line(last_line)
                        if decrypted:
                            last_entry = json.loads(decrypted)
                            self._last_hash = last_entry.get("entry_hash", "GENESIS")
                        else:
                            self._last_hash = "GENESIS"
                    else:
                        self._last_hash = "GENESIS"
                else:
                    self._last_hash = "GENESIS"
        except (json.JSONDecodeError, IOError, KeyError):
            self._last_hash = "GENESIS"

    def _compute_hash(self, data: str) -> str:
        """Compute SHA-256 hash of data."""
        import hashlib

        return hashlib.sha256(data.encode("utf-8")).hexdigest()

    def log_event(
        self, event_type: str, user_id: str, details: Dict[str, Any], phi_involved: bool = False
    ) -> str:
        """
        Log a compliance audit event with cryptographic chaining.

        Args:
            event_type: Type of event (e.g., "data_access", "auth_success")
            user_id: User who triggered the event
            details: Additional event details
            phi_involved: Whether PHI was involved (for HIPAA)

        Returns:
            The hash of the logged entry (for verification)
        """
        from datetime import datetime, timezone

        timestamp = datetime.now(timezone.utc).isoformat()

        # Create event with chain link to previous entry
        event = {
            "timestamp": timestamp,
            "event_type": event_type,
            "user_id": user_id,
            "phi_involved": phi_involved,
            "compliance_mode": self.config.mode.value,
            "details": details,
            "prev_hash": self._last_hash,  # Link to previous entry
        }

        # Compute hash of this entry on plaintext (excluding entry_hash itself)
        event_json = json.dumps(event, sort_keys=True)
        entry_hash = self._compute_hash(event_json)
        event["entry_hash"] = entry_hash

        # Serialize the full event
        event_line = json.dumps(event)

        # Encrypt if storage available
        if self._encrypted_storage and hasattr(self._encrypted_storage, "encrypt"):
            try:
                event_line = self._encrypted_storage.encrypt(event_line)
            except Exception as e:
                logger.warning(f"Failed to encrypt audit entry, writing plaintext: {e}")

        # Append to audit log (append-only for immutability)
        with open(self.audit_log_path, "a") as f:
            f.write(event_line + "\n")

        # Update chain state
        self._last_hash = entry_hash

        # Also log to standard logger
        log_msg = f"AUDIT: {event_type} by {user_id}"
        if phi_involved:
            log_msg += " [PHI]"
        logger.info(log_msg)

        return entry_hash

    def verify_integrity(self) -> Tuple[bool, List[str]]:
        """
        Verify the integrity of the entire audit log.

        Checks that:
        1. Each entry's hash matches its content
        2. The hash chain is unbroken (prev_hash links correctly)

        Returns:
            Tuple of (is_valid, list_of_errors)
        """
        errors = []

        if not self.audit_log_path.exists():
            return True, []

        try:
            with open(self.audit_log_path, "r") as f:
                lines = f.readlines()
        except IOError as e:
            return False, [f"Cannot read audit log: {e}"]

        expected_prev_hash = "GENESIS"

        for i, line in enumerate(lines, 1):
            line = line.strip()
            if not line:
                continue

            # Decrypt if needed
            decrypted = self._decrypt_line(line)
            if decrypted is None:
                errors.append(f"Line {i}: Cannot decrypt or parse entry")
                continue

            try:
                entry = json.loads(decrypted)
            except json.JSONDecodeError as e:
                errors.append(f"Line {i}: Invalid JSON - {e}")
                continue

            # Check prev_hash chain
            actual_prev_hash = entry.get("prev_hash")
            if actual_prev_hash != expected_prev_hash:
                errors.append(
                    f"Line {i}: Chain broken - expected prev_hash '{expected_prev_hash[:16]}...' "
                    f"but got '{actual_prev_hash[:16] if actual_prev_hash else 'None'}...'"
                )

            # Verify entry hash
            stored_hash = entry.get("entry_hash")
            if stored_hash:
                # Reconstruct entry without entry_hash to verify
                verify_entry = {k: v for k, v in entry.items() if k != "entry_hash"}
                verify_json = json.dumps(verify_entry, sort_keys=True)
                computed_hash = self._compute_hash(verify_json)

                if computed_hash != stored_hash:
                    errors.append(f"Line {i}: Hash mismatch - entry may have been tampered with")

                expected_prev_hash = stored_hash
            else:
                errors.append(f"Line {i}: Missing entry_hash")

        is_valid = len(errors) == 0

        if is_valid:
            logger.info(f"Audit log integrity verified: {len(lines)} entries OK")
        else:
            logger.warning(f"Audit log integrity check failed: {len(errors)} errors")

        return is_valid, errors

    def log_phi_access(self, user_id: str, action: str, data_type: str) -> None:
        """Log PHI access event (HIPAA-specific)."""
        if self.config.mode != ComplianceMode.HIPAA:
            return

        self.log_event(
            event_type="phi_access",
            user_id=user_id,
            details={"action": action, "data_type": data_type},
            phi_involved=True,
        )

    def log_authentication(self, user_id: str, success: bool, method: str) -> None:
        """Log authentication event."""
        self.log_event(
            event_type="authentication",
            user_id=user_id,
            details={"success": success, "method": method},
        )

    def log_authorization(self, user_id: str, action: str, allowed: bool, reason: str = "") -> None:
        """Log authorization decision."""
        self.log_event(
            event_type="authorization",
            user_id=user_id,
            details={"action": action, "allowed": allowed, "reason": reason},
        )

    def log_data_export(self, user_id: str, data_types: List[str], destination: str) -> None:
        """Log data export event."""
        self.log_event(
            event_type="data_export",
            user_id=user_id,
            details={"data_types": data_types, "destination": destination},
            phi_involved=self.config.mode == ComplianceMode.HIPAA,
        )

    def log_config_change(
        self, admin_id: str, setting: str, old_value: Any, new_value: Any
    ) -> None:
        """Log configuration change."""
        self.log_event(
            event_type="config_change",
            user_id=admin_id,
            details={"setting": setting, "old_value": str(old_value), "new_value": str(new_value)},
        )


def print_compliance_summary(mode: ComplianceMode) -> None:
    """Print a human-readable summary of compliance requirements."""
    profile = get_compliance_config(mode)

    print(f"\n{'=' * 60}")
    print(f"Compliance Profile: {mode.value.upper()}")
    print(f"{'=' * 60}\n")

    print("Data Protection:")
    print(f"  • Encryption required: {profile.encryption_required}")
    print(f"  • Encrypt sessions: {profile.encrypt_sessions}")
    print(f"  • Encrypt memory: {profile.encrypt_memory}")
    print(f"  • Encrypt history: {profile.encrypt_history}")
    print(f"  • PII redaction in logs: {profile.pii_redaction_in_logs}")

    print("\nAudit & Logging:")
    print(f"  • Audit logging required: {profile.audit_logging_required}")
    print(f"  • Log retention: {profile.log_retention_days} days")
    print(f"  • Log PHI access: {profile.log_phi_access}")
    print(f"  • Immutable audit log: {profile.immutable_audit_log}")

    print("\nAccess Control:")
    print(f"  • Session timeout: {profile.session_timeout_minutes} minutes")
    print(f"  • Re-auth for sensitive ops: {profile.require_reauth_for_sensitive}")
    print(f"  • Max trust level: {profile.max_trust_level}")
    print(f"  • MFA for admin: {profile.require_mfa_for_admin}")

    print("\nNetwork:")
    print(f"  • Require TLS: {profile.require_tls}")
    print(f"  • Local LLM required: {profile.local_llm_required}")

    if profile.phi_indicators:
        print(f"\nPHI Indicators ({len(profile.phi_indicators)} keywords):")
        print(f"  {', '.join(profile.phi_indicators[:10])}...")

    print()


# CLI interface
if __name__ == "__main__":
    import sys

    if len(sys.argv) < 2:
        print("Familiar Compliance Utility")
        print()
        print("Commands:")
        print("  show <mode>       Show compliance profile details")
        print("  validate <mode>   Validate current config against compliance")
        print("  modes             List available compliance modes")
        print()
        print("Modes: none, hipaa, soc2, pci_dss, gdpr")
        sys.exit(0)

    command = sys.argv[1]

    if command == "modes":
        print("Available compliance modes:")
        for mode in ComplianceMode:
            print(f"  • {mode.value}")

    elif command == "show" and len(sys.argv) > 2:
        try:
            mode = ComplianceMode(sys.argv[2].lower())
            print_compliance_summary(mode)
        except ValueError:
            print(f"Unknown mode: {sys.argv[2]}")
            print("Available: none, hipaa, soc2, pci_dss, gdpr")
            sys.exit(1)

    elif command == "validate" and len(sys.argv) > 2:
        try:
            mode = ComplianceMode(sys.argv[2].lower())
            # Load config (simplified - real impl would load from file)
            config = {
                "security": {
                    "encrypt_sessions": os.environ.get("FAMILIAR_ENCRYPTION_KEY") is not None,
                    "encrypt_memory": os.environ.get("FAMILIAR_ENCRYPTION_KEY") is not None,
                    "encrypt_history": os.environ.get("FAMILIAR_ENCRYPTION_KEY") is not None,
                },
                "observability": {
                    "audit_logging": True,
                    "log_retention_days": 90,
                },
            }

            errors = validate_compliance(config, mode)
            if errors:
                print(f"❌ Compliance validation failed for {mode.value}:")
                for error in errors:
                    print(f"   • {error}")
                sys.exit(1)
            else:
                print(f"✅ Configuration meets {mode.value} requirements")

        except ValueError:
            print(f"Unknown mode: {sys.argv[2]}")
            sys.exit(1)

    else:
        print(f"Unknown command: {command}")
        sys.exit(1)
