"""Custom exception hierarchy for P2HS."""
from typing import Any, Optional
from uuid import UUID

from fastapi import HTTPException


class P2HSError(Exception):
    """Base exception for P2HS application."""

    def __init__(self, message: str, code: str = "INTERNAL_ERROR"):
        self.message = message
        self.code = code
        super().__init__(message)


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

    def __init__(self, message: str = "Resource not found"):
        super().__init__(message, "NOT_FOUND")


class ForbiddenError(P2HSError):
    """Access forbidden."""

    def __init__(self, message: str = "Access forbidden"):
        super().__init__(message, "FORBIDDEN")


class UnauthorizedError(P2HSError):
    """Authentication required or invalid."""

    def __init__(self, message: str = "Unauthorized"):
        super().__init__(message, "UNAUTHORIZED")


class ConflictError(P2HSError):
    """Resource conflict."""

    def __init__(self, message: str = "Resource conflict"):
        super().__init__(message, "CONFLICT")


class ActiveRunRequiresRunIdError(P2HSError):
    """Project has an active run; client must pass run_id to continue."""

    def __init__(
        self,
        message: str = "This project has an active run awaiting continuation. Please resend the request with run_id.",
        *,
        run_id: Optional[UUID] = None,
        status: Optional[str] = None,
        pending_user_inputs: Optional[dict[str, Any]] = None,
        interruption_payload: Optional[dict[str, Any]] = None,
    ):
        super().__init__(message, "ACTIVE_RUN_REQUIRES_RUN_ID")
        self.run_id = run_id
        self.status = status
        self.pending_user_inputs = pending_user_inputs
        self.interruption_payload = interruption_payload


class RunBusyError(P2HSError):
    """Run is executing; cannot accept another message until it finishes or blocks."""

    def __init__(self, message: str = "This run is still executing. Wait for it to finish or pause."):
        super().__init__(message, "RUN_IN_PROGRESS")


class RunContinuationError(P2HSError):
    """run_id is not valid for continuation on this project."""

    def __init__(self, message: str = "Run not found or cannot be continued."):
        super().__init__(message, "RUN_CONTINUATION_INVALID")


class ValidationError(P2HSError):
    """Validation failed."""

    def __init__(self, message: str = "Validation failed", *, error: Optional[str] = None):
        super().__init__(message, "VALIDATION_ERROR")
        # When set, exposed as top-level ``error`` in JSON (e.g. ``mapping_failed``, ``silent_audio``).
        self.public_error = error


class RateLimitError(P2HSError):
    """Rate limit exceeded."""

    def __init__(self, message: str = "Rate limit exceeded"):
        super().__init__(message, "RATE_LIMITED")


class PaymentRequiredError(P2HSError):
    """Active subscription required for workspace access."""

    def __init__(
        self,
        message: str = "Active subscription required for this workspace.",
        workspace_id: Optional[str] = None,
        billing_status: Optional[str] = None,
    ):
        super().__init__(message, "PAYMENT_REQUIRED")
        self.workspace_id = workspace_id
        self.billing_status = billing_status


class MfaRequiredError(P2HSError):
    """Paid workspace requires at least one MFA method before productive access."""

    def __init__(
        self,
        message: str = "MFA enrollment required for this workspace.",
        workspace_id: Optional[str] = None,
    ):
        super().__init__(message, "MFA_REQUIRED")
        self.workspace_id = workspace_id


class PlanLimitError(P2HSError):
    """Plan limit reached (e.g. max members)."""

    def __init__(
        self,
        message: str,
        limit_code: str = "max_members",
        current_count: Optional[int] = None,
        max_allowed: Optional[int] = None,
    ):
        super().__init__(message, "PLAN_LIMIT_REACHED")
        self.limit_code = limit_code
        self.current_count = current_count
        self.max_allowed = max_allowed


class InvitePathMismatchError(P2HSError):
    """Invite token does not belong to workspace in path."""

    def __init__(self, message: str = "Invite token does not belong to this workspace.", workspace_id: Optional[str] = None):
        super().__init__(message, "INVITE_PATH_MISMATCH")
        self.workspace_id = workspace_id


def to_http_exception(exc: P2HSError) -> HTTPException:
    """Convert P2HS exception to HTTPException."""
    status_map = {
        "NOT_FOUND": 404,
        "FORBIDDEN": 403,
        "UNAUTHORIZED": 401,
        "CONFLICT": 409,
        "SIGNUP_REQUIRED": 409,
        "ACTIVE_RUN_REQUIRES_RUN_ID": 409,
        "RUN_IN_PROGRESS": 409,
        "RUN_CONTINUATION_INVALID": 409,
        "VALIDATION_ERROR": 422,
        "RATE_LIMITED": 429,
        "PAYMENT_REQUIRED": 402,
        "MFA_REQUIRED": 403,
        "PLAN_LIMIT_REACHED": 403,
        "INVITE_PATH_MISMATCH": 422,
        "INTERNAL_ERROR": 500,
        "INTAKE_UNAVAILABLE": 503,
    }
    status = status_map.get(exc.code, 500)
    detail = {"error": exc.code.lower(), "message": exc.message}
    if isinstance(exc, ValidationError) and exc.public_error:
        detail["error"] = exc.public_error
    if isinstance(exc, PaymentRequiredError):
        if exc.workspace_id:
            detail["workspace_id"] = exc.workspace_id
        if exc.billing_status:
            detail["billing_status"] = exc.billing_status
    elif isinstance(exc, MfaRequiredError):
        if exc.workspace_id:
            detail["workspace_id"] = exc.workspace_id
    elif isinstance(exc, PlanLimitError):
        detail["limit_code"] = exc.limit_code
        if exc.current_count is not None:
            detail["current_count"] = exc.current_count
        if exc.max_allowed is not None:
            detail["max_allowed"] = exc.max_allowed
    elif isinstance(exc, InvitePathMismatchError):
        if exc.workspace_id:
            detail["workspace_id"] = exc.workspace_id
    elif isinstance(exc, ActiveRunRequiresRunIdError):
        if exc.run_id is not None:
            detail["run_id"] = str(exc.run_id)
        if exc.status:
            detail["status"] = exc.status
        if exc.pending_user_inputs is not None:
            detail["pending_user_inputs"] = exc.pending_user_inputs
        if exc.interruption_payload is not None:
            detail["interruption_payload"] = exc.interruption_payload
    elif exc.code == "SIGNUP_REQUIRED":
        detail["signup_url"] = exc.signup_url
    return HTTPException(status_code=status, detail=detail)
