"""Audit integration for OCLAWMA job queue.

This module provides wrappers and utilities to integrate audit logging
with job queue operations.

Example:
    >>> from oclawma.audit_integration import AuditedJobQueue
    >>> queue = AuditedJobQueue("~/.oclawma/queue.db")
    >>> job = queue.enqueue({"task": "backup"}, user="admin")
    >>> queue.complete(job.id, user="admin")
"""

from __future__ import annotations

import functools
import logging
from typing import Any, Callable, ParamSpec, TypeVar

from oclawma.audit import AuditAction, AuditEvent, log_audit
from oclawma.queue import Job, JobPriority, JobQueue

logger = logging.getLogger(__name__)

P = ParamSpec("P")
T = TypeVar("T")


def _get_user(kwargs: dict[str, Any]) -> str:
    """Extract user from kwargs or return default."""
    return kwargs.pop("user", "system")


def _audit_job_operation(
    action: AuditAction,
    get_job_id: Callable[..., int | None],  # type: ignore[valid-type]
    get_details: Callable[..., dict[str, Any]] | None = None,  # type: ignore[valid-type]
) -> Callable[[Callable[P, T]], Callable[P, T]]:  # type: ignore[valid-type]
    """Decorator to audit job operations.

    Args:
        action: The audit action to log
        get_job_id: Function to extract job_id from args/kwargs
        get_details: Optional function to extract details from args/kwargs

    Returns:
        Decorator function
    """

    def decorator(func: Callable[P, T]) -> Callable[P, T]:  # type: ignore[valid-type]
        @functools.wraps(func)
        def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
            # Extract user before passing to function
            user = _get_user(kwargs)

            # Call the function
            result = func(*args, **kwargs)

            # Get job_id from result or args
            job_id = get_job_id(result, *args, **kwargs)

            # Build details
            details = {}
            if get_details:
                details = get_details(result, *args, **kwargs)

            # Log the audit event
            try:
                log_audit(action, user=user, job_id=job_id, details=details)
            except Exception as e:
                logger.warning(f"Failed to log audit event: {e}")

            return result

        return wrapper

    return decorator


class AuditedJobQueue(JobQueue):
    """JobQueue with automatic audit logging.

    This class extends JobQueue to automatically log audit events
    for job lifecycle operations.

    Example:
        >>> queue = AuditedJobQueue("~/.oclawma/queue.db")
        >>> # All operations are automatically audited
        >>> job = queue.enqueue({"task": "backup"}, user="admin")
        >>> queue.complete(job.id, user="worker1")
    """

    def enqueue(
        self,
        payload: dict[str, Any],
        priority: JobPriority | int = JobPriority.NORMAL,
        scheduled_at: Any = None,
        max_retries: int | None = None,
        depends_on: list[int] | None = None,
        job_type: str = "default",
        user: str = "system",
    ) -> Job:
        """Add a new job to the queue with audit logging.

        Args:
            payload: Job data/payload
            priority: Job priority level
            scheduled_at: When to run the job
            max_retries: Max retries for this job
            depends_on: List of job IDs that must complete first
            job_type: Type of job
            user: User creating the job

        Returns:
            The created job
        """
        job = super().enqueue(
            payload=payload,
            priority=priority,
            scheduled_at=scheduled_at,
            max_retries=max_retries,
            depends_on=depends_on,
            job_type=job_type,
        )

        # Log audit event
        try:
            log_audit(
                AuditAction.CREATE,
                user=user,
                job_id=job.id,
                details={
                    "job_type": job_type,
                    "priority": str(priority),
                    "has_dependencies": depends_on is not None,
                    "scheduled": scheduled_at is not None,
                },
            )
        except Exception as e:
            logger.warning(f"Failed to log audit event: {e}")

        return job

    def complete(self, job_id: int, user: str = "system") -> Job:
        """Mark a job as completed with audit logging.

        Args:
            job_id: Job identifier
            user: User completing the job

        Returns:
            The updated job
        """
        # Get job details before completion for audit
        try:
            job = self.get_job(job_id, decrypt=False)
            job_type = job.job_type
            retry_count = job.retry_count
        except Exception:
            job_type = "unknown"
            retry_count = 0

        result = super().complete(job_id)

        # Log audit event
        try:
            log_audit(
                AuditAction.COMPLETE,
                user=user,
                job_id=job_id,
                details={
                    "job_type": job_type,
                    "retry_count": retry_count,
                },
            )
        except Exception as e:
            logger.warning(f"Failed to log audit event: {e}")

        return result

    def fail(self, job_id: int, error: str | None = None, user: str = "system") -> Job:
        """Mark a job as failed with audit logging.

        Args:
            job_id: Job identifier
            error: Error message
            user: User marking the job as failed

        Returns:
            The updated job
        """
        # Get job details before failure for audit
        try:
            job = self.get_job(job_id, decrypt=False)
            job_type = job.job_type
            retry_count = job.retry_count
            max_retries = job.max_retries
            is_retryable = job.is_retryable()
        except Exception:
            job_type = "unknown"
            retry_count = 0
            max_retries = 0
            is_retryable = False

        result = super().fail(job_id, error)

        # Log audit event
        try:
            log_audit(
                AuditAction.FAIL,
                user=user,
                job_id=job_id,
                details={
                    "job_type": job_type,
                    "retry_count": retry_count,
                    "max_retries": max_retries,
                    "is_retryable": is_retryable,
                    "error_preview": error[:200] if error else None,
                },
            )
        except Exception as e:
            logger.warning(f"Failed to log audit event: {e}")

        return result

    def cancel(self, job_id: int, user: str = "system") -> bool:
        """Cancel a job with audit logging.

        Args:
            job_id: Job identifier
            user: User cancelling the job

        Returns:
            True if cancelled, False otherwise
        """
        # Get job details before cancellation for audit
        try:
            job = self.get_job(job_id, decrypt=False)
            job_type = job.job_type
            status = job.status.value
        except Exception:
            job_type = "unknown"
            status = "unknown"

        result = super().cancel(job_id)

        if result:
            # Log audit event only if actually cancelled
            try:
                log_audit(
                    AuditAction.CANCEL,
                    user=user,
                    job_id=job_id,
                    details={
                        "job_type": job_type,
                        "previous_status": status,
                    },
                )
            except Exception as e:
                logger.warning(f"Failed to log audit event: {e}")

        return result

    def update_job(
        self,
        job_id: int,
        updates: dict[str, Any],
        user: str = "system",
    ) -> Job:
        """Update a job with audit logging.

        Args:
            job_id: Job identifier
            updates: Dictionary of fields to update
            user: User making the update

        Returns:
            The updated job

        Raises:
            JobNotFoundError: If job not found
        """
        from oclawma.queue.store import JobNotFoundError

        # Get job before update
        try:
            job_before = self.get_job(job_id, decrypt=False)
            old_values = {k: getattr(job_before, k) for k in updates if hasattr(job_before, k)}
        except JobNotFoundError:
            raise

        # Apply updates directly through store
        for key, value in updates.items():
            if hasattr(job_before, key):
                setattr(job_before, key, value)
        job_before.updated_at = __import__("datetime").datetime.utcnow()
        result = self.store.update(job_before)

        # Log audit event
        try:
            log_audit(
                AuditAction.UPDATE,
                user=user,
                job_id=job_id,
                details={
                    "fields_updated": list(updates.keys()),
                    "old_values": {k: str(v) for k, v in old_values.items()},
                },
            )
        except Exception as e:
            logger.warning(f"Failed to log audit event: {e}")

        return result


def log_job_create(job: Job, user: str = "system") -> AuditEvent | None:
    """Log a job creation event.

    Args:
        job: The created job
        user: User who created the job

    Returns:
        The logged AuditEvent or None
    """
    return log_audit(
        AuditAction.CREATE,
        user=user,
        job_id=job.id,
        details={
            "job_type": job.job_type,
            "priority": job.priority.value,
            "has_dependencies": job.depends_on is not None,
            "scheduled": job.scheduled_at is not None,
        },
    )


def log_job_complete(job_id: int, user: str = "system", **details: Any) -> AuditEvent | None:
    """Log a job completion event.

    Args:
        job_id: The job ID
        user: User who completed the job
        **details: Additional details to log

    Returns:
        The logged AuditEvent or None
    """
    return log_audit(AuditAction.COMPLETE, user=user, job_id=job_id, details=details)


def log_job_fail(
    job_id: int, error: str | None = None, user: str = "system", **details: Any
) -> AuditEvent | None:
    """Log a job failure event.

    Args:
        job_id: The job ID
        error: Error message
        user: User who marked the job as failed
        **details: Additional details to log

    Returns:
        The logged AuditEvent or None
    """
    return log_audit(
        AuditAction.FAIL,
        user=user,
        job_id=job_id,
        details={"error_preview": error[:200] if error else None, **details},
    )


def log_job_cancel(
    job_id: int, reason: str | None = None, user: str = "system"
) -> AuditEvent | None:
    """Log a job cancellation event.

    Args:
        job_id: The job ID
        reason: Cancellation reason
        user: User who cancelled the job

    Returns:
        The logged AuditEvent or None
    """
    return log_audit(
        AuditAction.CANCEL, user=user, job_id=job_id, details={"reason": reason} if reason else {}
    )


def log_job_update(
    job_id: int, fields: list[str], user: str = "system", **details: Any
) -> AuditEvent | None:
    """Log a job update event.

    Args:
        job_id: The job ID
        fields: List of fields that were updated
        user: User who updated the job
        **details: Additional details to log

    Returns:
        The logged AuditEvent or None
    """
    return log_audit(
        AuditAction.UPDATE, user=user, job_id=job_id, details={"fields_updated": fields, **details}
    )
