"""Domain error hierarchy with gRPC status mapping.

Provides a central taxonomy for domain errors that can be automatically
mapped to appropriate gRPC status codes. This eliminates scattered
abort() calls and provides consistent error handling across the system.
"""


from __future__ import annotations

from enum import Enum
from typing import TYPE_CHECKING

import grpc

if TYPE_CHECKING:
    from grpc import StatusCode as GrpcStatusCode
else:
    class GrpcStatusCode(Enum):
        pass

StatusCode: type[GrpcStatusCode] = grpc.StatusCode


class ErrorCode(Enum):
    """Standard error codes with gRPC status mapping.

    Each error code maps to a gRPC StatusCode for consistent API responses.
    """

    # Not found errors → NOT_FOUND
    MEETING_NOT_FOUND = ("meeting_not_found", StatusCode.NOT_FOUND)
    SEGMENT_NOT_FOUND = ("segment_not_found", StatusCode.NOT_FOUND)
    ENTITY_NOT_FOUND = ("entity_not_found", StatusCode.NOT_FOUND)
    JOB_NOT_FOUND = ("job_not_found", StatusCode.NOT_FOUND)
    ANNOTATION_NOT_FOUND = ("annotation_not_found", StatusCode.NOT_FOUND)
    INTEGRATION_NOT_FOUND = ("integration_not_found", StatusCode.NOT_FOUND)
    WEBHOOK_NOT_FOUND = ("webhook_not_found", StatusCode.NOT_FOUND)
    SUMMARY_NOT_FOUND = ("summary_not_found", StatusCode.NOT_FOUND)

    # Validation errors → INVALID_ARGUMENT
    INVALID_MEETING_ID = ("invalid_meeting_id", StatusCode.INVALID_ARGUMENT)
    INVALID_SEGMENT_ID = ("invalid_segment_id", StatusCode.INVALID_ARGUMENT)
    INVALID_REQUEST = ("invalid_request", StatusCode.INVALID_ARGUMENT)
    MISSING_REQUIRED_FIELD = ("missing_required_field", StatusCode.INVALID_ARGUMENT)
    INVALID_FORMAT = ("invalid_format", StatusCode.INVALID_ARGUMENT)

    # State errors → FAILED_PRECONDITION
    MEETING_ALREADY_STOPPED = ("meeting_already_stopped", StatusCode.FAILED_PRECONDITION)
    MEETING_NOT_STOPPED = ("meeting_not_stopped", StatusCode.FAILED_PRECONDITION)
    JOB_ALREADY_RUNNING = ("job_already_running", StatusCode.FAILED_PRECONDITION)
    CONSENT_REQUIRED = ("consent_required", StatusCode.FAILED_PRECONDITION)
    DATABASE_REQUIRED = ("database_required", StatusCode.FAILED_PRECONDITION)

    # Conflict errors → ALREADY_EXISTS
    RESOURCE_ALREADY_EXISTS = ("resource_already_exists", StatusCode.ALREADY_EXISTS)

    # Permission errors → PERMISSION_DENIED
    WORKSPACE_ACCESS_DENIED = ("workspace_access_denied", StatusCode.PERMISSION_DENIED)
    PROJECT_ACCESS_DENIED = ("project_access_denied", StatusCode.PERMISSION_DENIED)

    # Resource errors → RESOURCE_EXHAUSTED
    RATE_LIMITED = ("rate_limited", StatusCode.RESOURCE_EXHAUSTED)
    QUOTA_EXCEEDED = ("quota_exceeded", StatusCode.RESOURCE_EXHAUSTED)

    # Service errors → UNAVAILABLE
    SERVICE_UNAVAILABLE = ("service_unavailable", StatusCode.UNAVAILABLE)
    PROVIDER_UNAVAILABLE = ("provider_unavailable", StatusCode.UNAVAILABLE)

    # Internal errors → INTERNAL
    PROVIDER_ERROR = ("provider_error", StatusCode.INTERNAL)
    DATABASE_ERROR = ("database_error", StatusCode.INTERNAL)
    INTERNAL_ERROR = ("internal_error", StatusCode.INTERNAL)

    # Cancelled errors → CANCELLED
    OPERATION_CANCELLED = ("operation_cancelled", StatusCode.CANCELLED)

    def __init__(self, code: str, grpc_status: GrpcStatusCode) -> None:
        """Initialize error code with string code and gRPC status."""
        self._code = code
        self._grpc_status = grpc_status

    @property
    def code(self) -> str:
        """String code for the error."""
        return self._code

    @property
    def grpc_status(self) -> GrpcStatusCode:
        """gRPC status code for this error."""
        return self._grpc_status


class DomainError(Exception):
    """Base class for domain errors.

    All domain errors carry an ErrorCode that maps to gRPC status,
    a human-readable message, and optional structured details.
    """

    def __init__(
        self,
        error_code: ErrorCode,
        message: str,
        *,
        details: dict[str, object] | None = None,
    ) -> None:
        """Initialize domain error.

        Args:
            error_code: The error code with gRPC mapping.
            message: Human-readable error message.
            details: Optional structured details for debugging.
        """
        super().__init__(message)
        self.error_code = error_code
        self.message = message
        self.details = details or {}

    @property
    def grpc_status(self) -> GrpcStatusCode:
        """gRPC status code for this error."""
        return self.error_code.grpc_status


class NotFoundError(DomainError):
    """Resource not found."""

    def __init__(
        self,
        resource_type: str,
        resource_id: str,
        *,
        error_code: ErrorCode | None = None,
    ) -> None:
        """Initialize not found error.

        Args:
            resource_type: Type of resource (e.g., "meeting", "segment").
            resource_id: ID of the missing resource.
            error_code: Optional specific error code, auto-detected if not provided.
        """
        if error_code is None:
            # Try to auto-detect error code from resource type
            error_code_name = f"{resource_type.upper()}_NOT_FOUND"
            try:
                error_code = ErrorCode[error_code_name]
            except KeyError:
                error_code = ErrorCode.ENTITY_NOT_FOUND

        super().__init__(
            error_code,
            f"{resource_type.capitalize()} '{resource_id}' not found",
            details={"resource_type": resource_type, "resource_id": resource_id},
        )


class ValidationError(DomainError):
    """Request validation failed."""

    def __init__(
        self,
        field: str,
        reason: str,
        *,
        error_code: ErrorCode = ErrorCode.INVALID_REQUEST,
    ) -> None:
        """Initialize validation error.

        Args:
            field: Field that failed validation.
            reason: Reason for validation failure.
            error_code: Specific error code (defaults to INVALID_REQUEST).
        """
        super().__init__(
            error_code,
            f"Validation failed for '{field}': {reason}",
            details={"field": field, "reason": reason},
        )


class StateError(DomainError):
    """Operation cannot proceed due to current state."""

    def __init__(
        self,
        error_code: ErrorCode,
        message: str,
        *,
        current_state: str | None = None,
        required_state: str | None = None,
    ) -> None:
        """Initialize state error.

        Args:
            error_code: The specific error code.
            message: Human-readable error message.
            current_state: Optional current state description.
            required_state: Optional required state description.
        """
        details: dict[str, object] = {}
        if current_state:
            details["current_state"] = current_state
        if required_state:
            details["required_state"] = required_state
        super().__init__(error_code, message, details=details)


class ProviderError(DomainError):
    """External provider operation failed."""

    def __init__(
        self,
        provider_name: str,
        operation: str,
        reason: str,
        *,
        error_code: ErrorCode = ErrorCode.PROVIDER_ERROR,
    ) -> None:
        """Initialize provider error.

        Args:
            provider_name: Name of the provider (e.g., "openai", "anthropic").
            operation: Operation that failed (e.g., "summarize", "transcribe").
            reason: Reason for failure.
            error_code: Specific error code (defaults to PROVIDER_ERROR).
        """
        super().__init__(
            error_code,
            f"Provider '{provider_name}' failed during '{operation}': {reason}",
            details={
                "provider_name": provider_name,
                "operation": operation,
                "reason": reason,
            },
        )


class ConsentRequiredError(DomainError):
    """Operation requires user consent that has not been granted."""

    def __init__(
        self,
        consent_type: str,
        operation: str,
    ) -> None:
        """Initialize consent required error.

        Args:
            consent_type: Type of consent required (e.g., "cloud_consent").
            operation: Operation that requires consent.
        """
        super().__init__(
            ErrorCode.CONSENT_REQUIRED,
            f"'{consent_type}' consent required for '{operation}'",
            details={"consent_type": consent_type, "operation": operation},
        )


class DatabaseRequiredError(DomainError):
    """Operation requires database but none is configured."""

    def __init__(self, feature: str) -> None:
        """Initialize database required error.

        Args:
            feature: Feature that requires database.
        """
        super().__init__(
            ErrorCode.DATABASE_REQUIRED,
            f"'{feature}' requires database persistence",
            details={"feature": feature},
        )


class PermissionDeniedError(DomainError):
    """Raise when user lacks permission for the requested operation."""

    def __init__(
        self,
        message: str,
        *,
        resource_type: str | None = None,
        resource_id: str | None = None,
        required_permission: str | None = None,
    ) -> None:
        """Initialize permission denied error.

        Args:
            message: Human-readable error message.
            resource_type: Type of resource (e.g., "project", "workspace").
            resource_id: ID of the resource.
            required_permission: Permission that was required.
        """
        details: dict[str, object] = {}
        if resource_type:
            details["resource_type"] = resource_type
        if resource_id:
            details["resource_id"] = resource_id
        if required_permission:
            details["required_permission"] = required_permission
        super().__init__(
            ErrorCode.PROJECT_ACCESS_DENIED,
            message,
            details=details,
        )


class CannotArchiveDefaultProjectError(DomainError):
    """Raise when attempting to archive the default project for a workspace."""

    def __init__(self, project_id: str) -> None:
        """Initialize cannot archive default project error.

        Args:
            project_id: ID of the default project.
        """
        from noteflow.config.constants import ERROR_DETAIL_PROJECT_ID

        super().__init__(
            ErrorCode.INVALID_REQUEST,
            f"Cannot archive default project '{project_id}'",
            details={ERROR_DETAIL_PROJECT_ID: project_id},
        )
