"""Audit logging for sensitive operations.

Implements:
- Immutable audit log entries
- Sensitive operation tracking
- 1 year retention policy
- Compliance-ready logging
"""

import json
import hashlib
from datetime import datetime, timedelta
from typing import Optional, Any
from enum import Enum
from uuid import UUID

from sqlalchemy import (
    Column,
    String,
    DateTime,
    Text,
    Index,
    create_engine,
)
from sqlalchemy.orm import declarative_base, Session
from sqlalchemy.dialects.postgresql import JSONB, UUID as PG_UUID

from src.core.config import settings
from src.core.logging_config import get_logger, get_correlation_id


logger = get_logger(__name__)
Base = declarative_base()


class AuditEventType(str, Enum):
    """Types of audit events."""

    # Authentication events
    LOGIN_SUCCESS = "login_success"
    LOGIN_FAILURE = "login_failure"
    LOGOUT = "logout"
    PASSWORD_CHANGE = "password_change"
    PASSWORD_RESET_REQUEST = "password_reset_request"
    PASSWORD_RESET_COMPLETE = "password_reset_complete"
    TOKEN_REFRESH = "token_refresh"

    # API Key events
    API_KEY_CREATED = "api_key_created"
    API_KEY_REVOKED = "api_key_revoked"
    API_KEY_USED = "api_key_used"

    # User events
    USER_REGISTERED = "user_registered"
    USER_UPDATED = "user_updated"
    USER_DEACTIVATED = "user_deactivated"

    # Scenario events
    SCENARIO_CREATED = "scenario_created"
    SCENARIO_UPDATED = "scenario_updated"
    SCENARIO_DELETED = "scenario_deleted"
    SCENARIO_STARTED = "scenario_started"
    SCENARIO_STOPPED = "scenario_stopped"
    SCENARIO_ARCHIVED = "scenario_archived"

    # Report events
    REPORT_GENERATED = "report_generated"
    REPORT_DOWNLOADED = "report_downloaded"
    REPORT_DELETED = "report_deleted"

    # Admin events
    ADMIN_ACCESS = "admin_access"
    CONFIG_CHANGED = "config_changed"

    # Security events
    SUSPICIOUS_ACTIVITY = "suspicious_activity"
    RATE_LIMIT_EXCEEDED = "rate_limit_exceeded"
    PERMISSION_DENIED = "permission_denied"


class AuditLogEntry(Base):
    """Audit log entry database model."""

    __tablename__ = "audit_log"

    id = Column(PG_UUID(as_uuid=True), primary_key=True)
    timestamp = Column(DateTime, nullable=False, default=datetime.utcnow)
    event_type = Column(String(50), nullable=False, index=True)
    user_id = Column(String(36), nullable=True, index=True)
    user_email = Column(String(255), nullable=True)
    ip_address = Column(String(45), nullable=True)  # IPv6 compatible
    user_agent = Column(Text, nullable=True)
    resource_type = Column(String(50), nullable=True)
    resource_id = Column(String(36), nullable=True)
    action = Column(String(50), nullable=False)
    status = Column(String(20), nullable=False)  # success, failure
    details = Column(JSONB, nullable=True)
    correlation_id = Column(String(36), nullable=True, index=True)

    # Integrity hash for immutability verification
    integrity_hash = Column(String(64), nullable=False)

    # Indexes for common queries
    __table_args__ = (
        Index("idx_audit_timestamp", "timestamp"),
        Index("idx_audit_event_type_timestamp", "event_type", "timestamp"),
        Index("idx_audit_user_timestamp", "user_id", "timestamp"),
    )

    def calculate_integrity_hash(self) -> str:
        """Calculate integrity hash for the entry."""
        data = {
            "id": str(self.id),
            "timestamp": self.timestamp.isoformat() if self.timestamp else None,
            "event_type": self.event_type,
            "user_id": self.user_id,
            "resource_type": self.resource_type,
            "resource_id": self.resource_id,
            "action": self.action,
            "status": self.status,
            "details": self.details,
        }

        # Sort keys for consistent hashing
        data_str = json.dumps(data, sort_keys=True, default=str)
        return hashlib.sha256(data_str.encode()).hexdigest()

    def verify_integrity(self) -> bool:
        """Verify entry integrity."""
        return self.integrity_hash == self.calculate_integrity_hash()


class AuditLogger:
    """Audit logger for sensitive operations."""

    def __init__(self):
        self._session: Optional[Session] = None
        self._enabled = getattr(settings, "audit_logging_enabled", True)

    def _get_session(self) -> Session:
        """Get database session for audit logging."""
        if self._session is None:
            # Use separate connection for audit logs (immutable storage)
            audit_db_url = getattr(
                settings,
                "audit_database_url",
                settings.database_url,
            )
            engine = create_engine(audit_db_url.replace("+asyncpg", ""))
            Base.metadata.create_all(engine)
            self._session = Session(bind=engine)
        return self._session

    def log(
        self,
        event_type: AuditEventType,
        action: str,
        user_id: Optional[UUID] = None,
        user_email: Optional[str] = None,
        ip_address: Optional[str] = None,
        user_agent: Optional[str] = None,
        resource_type: Optional[str] = None,
        resource_id: Optional[UUID] = None,
        status: str = "success",
        details: Optional[dict] = None,
    ) -> Optional[AuditLogEntry]:
        """Log an audit event.

        Args:
            event_type: Type of audit event
            action: Action performed
            user_id: User ID who performed the action
            user_email: User email
            ip_address: Client IP address
            user_agent: Client user agent
            resource_type: Type of resource affected
            resource_id: ID of resource affected
            status: Action status (success/failure)
            details: Additional details

        Returns:
            Created audit log entry or None if disabled
        """
        if not self._enabled:
            return None

        try:
            from uuid import uuid4

            entry = AuditLogEntry(
                id=uuid4(),
                timestamp=datetime.utcnow(),
                event_type=event_type.value,
                user_id=str(user_id) if user_id else None,
                user_email=user_email,
                ip_address=ip_address,
                user_agent=user_agent,
                resource_type=resource_type,
                resource_id=str(resource_id) if resource_id else None,
                action=action,
                status=status,
                details=details or {},
                correlation_id=get_correlation_id(),
            )

            # Calculate integrity hash
            entry.integrity_hash = entry.calculate_integrity_hash()

            # Save to database
            session = self._get_session()
            session.add(entry)
            session.commit()

            # Also log to structured logger for real-time monitoring
            logger.info(
                "Audit event",
                extra={
                    "audit_event": event_type.value,
                    "user_id": str(user_id) if user_id else None,
                    "action": action,
                    "status": status,
                    "resource_id": str(resource_id) if resource_id else None,
                },
            )

            return entry

        except Exception as e:
            logger.error(f"Failed to write audit log: {e}")
            # Fallback to regular logging
            logger.warning(
                "Audit log fallback",
                extra={
                    "event_type": event_type.value,
                    "action": action,
                    "user_id": str(user_id) if user_id else None,
                    "error": str(e),
                },
            )
            return None

    def log_auth_event(
        self,
        event_type: AuditEventType,
        user_id: Optional[UUID] = None,
        user_email: Optional[str] = None,
        ip_address: Optional[str] = None,
        user_agent: Optional[str] = None,
        status: str = "success",
        details: Optional[dict] = None,
    ) -> Optional[AuditLogEntry]:
        """Log authentication event."""
        return self.log(
            event_type=event_type,
            action=event_type.value,
            user_id=user_id,
            user_email=user_email,
            ip_address=ip_address,
            user_agent=user_agent,
            status=status,
            details=details,
        )

    def log_api_key_event(
        self,
        event_type: AuditEventType,
        api_key_id: str,
        user_id: UUID,
        ip_address: Optional[str] = None,
        status: str = "success",
        details: Optional[dict] = None,
    ) -> Optional[AuditLogEntry]:
        """Log API key event."""
        return self.log(
            event_type=event_type,
            action=event_type.value,
            user_id=user_id,
            resource_type="api_key",
            resource_id=UUID(api_key_id) if isinstance(api_key_id, str) else api_key_id,
            ip_address=ip_address,
            status=status,
            details=details,
        )

    def log_scenario_event(
        self,
        event_type: AuditEventType,
        scenario_id: UUID,
        user_id: UUID,
        ip_address: Optional[str] = None,
        status: str = "success",
        details: Optional[dict] = None,
    ) -> Optional[AuditLogEntry]:
        """Log scenario event."""
        return self.log(
            event_type=event_type,
            action=event_type.value,
            user_id=user_id,
            resource_type="scenario",
            resource_id=scenario_id,
            ip_address=ip_address,
            status=status,
            details=details,
        )

    def query_logs(
        self,
        user_id: Optional[UUID] = None,
        event_type: Optional[AuditEventType] = None,
        start_date: Optional[datetime] = None,
        end_date: Optional[datetime] = None,
        limit: int = 100,
    ) -> list[AuditLogEntry]:
        """Query audit logs.

        Args:
            user_id: Filter by user ID
            event_type: Filter by event type
            start_date: Filter by start date
            end_date: Filter by end date
            limit: Maximum results

        Returns:
            List of audit log entries
        """
        session = self._get_session()
        query = session.query(AuditLogEntry)

        if user_id:
            query = query.filter(AuditLogEntry.user_id == str(user_id))

        if event_type:
            query = query.filter(AuditLogEntry.event_type == event_type.value)

        if start_date:
            query = query.filter(AuditLogEntry.timestamp >= start_date)

        if end_date:
            query = query.filter(AuditLogEntry.timestamp <= end_date)

        return query.order_by(AuditLogEntry.timestamp.desc()).limit(limit).all()

    def cleanup_old_logs(self, retention_days: int = 365) -> int:
        """Clean up audit logs older than retention period.

        Note: In production, this should archive logs before deletion.

        Args:
            retention_days: Number of days to retain logs

        Returns:
            Number of entries deleted
        """
        cutoff_date = datetime.utcnow() - timedelta(days=retention_days)

        session = self._get_session()
        result = (
            session.query(AuditLogEntry)
            .filter(AuditLogEntry.timestamp < cutoff_date)
            .delete()
        )
        session.commit()

        logger.info(f"Cleaned up {result} old audit log entries")
        return result


# Global audit logger instance
audit_logger = AuditLogger()


# Convenience functions


def log_login(
    user_id: UUID,
    user_email: str,
    ip_address: str,
    user_agent: str,
    success: bool = True,
    failure_reason: Optional[str] = None,
) -> None:
    """Log login attempt."""
    audit_logger.log_auth_event(
        event_type=AuditEventType.LOGIN_SUCCESS
        if success
        else AuditEventType.LOGIN_FAILURE,
        user_id=user_id,
        user_email=user_email,
        ip_address=ip_address,
        user_agent=user_agent,
        status="success" if success else "failure",
        details={"failure_reason": failure_reason} if not success else None,
    )


def log_password_change(
    user_id: UUID,
    user_email: str,
    ip_address: str,
) -> None:
    """Log password change."""
    audit_logger.log_auth_event(
        event_type=AuditEventType.PASSWORD_CHANGE,
        user_id=user_id,
        user_email=user_email,
        ip_address=ip_address,
    )


def log_api_key_created(
    api_key_id: str,
    user_id: UUID,
    ip_address: str,
) -> None:
    """Log API key creation."""
    audit_logger.log_api_key_event(
        event_type=AuditEventType.API_KEY_CREATED,
        api_key_id=api_key_id,
        user_id=user_id,
        ip_address=ip_address,
    )


def log_api_key_revoked(
    api_key_id: str,
    user_id: UUID,
    ip_address: str,
) -> None:
    """Log API key revocation."""
    audit_logger.log_api_key_event(
        event_type=AuditEventType.API_KEY_REVOKED,
        api_key_id=api_key_id,
        user_id=user_id,
        ip_address=ip_address,
    )


def log_suspicious_activity(
    user_id: Optional[UUID],
    ip_address: str,
    activity_type: str,
    details: dict,
) -> None:
    """Log suspicious activity."""
    audit_logger.log(
        event_type=AuditEventType.SUSPICIOUS_ACTIVITY,
        action=activity_type,
        user_id=user_id,
        ip_address=ip_address,
        status="detected",
        details=details,
    )
