"""Define package errors.

This module contains all custom exceptions for the aiokwikset library.
Exceptions are organized in a hierarchy with KwiksetError as the base class.
"""


class KwiksetError(Exception):
    """Define a base error for all Kwikset-related exceptions."""


class RequestError(KwiksetError):
    """Define an error related to invalid HTTP requests."""


class AuthenticationError(KwiksetError):
    """Define a base error for authentication-related issues."""


class Unauthenticated(AuthenticationError):
    """Raised when authentication credentials are invalid or missing."""


class UserNotFound(AuthenticationError):
    """Raised when a user is not found in the system."""


class UserExists(AuthenticationError):
    """Raised when attempting to create a user that already exists."""


class UserNotConfirmed(AuthenticationError):
    """Raised when a user has not confirmed their email address."""


class PasswordChangeRequired(AuthenticationError):
    """Raised when a password change is required.

    :param message: The error message
    :type message: ``str``
    """

    def __init__(self, message: str = "Password change required.") -> None:
        """Initialize a password change required error."""
        super().__init__(message)


class MFAChallengeRequired(AuthenticationError):
    """Raised when MFA challenge is required during authentication.

    :param message: The error message
    :type message: ``str``
    :param mfa_type: The type of MFA challenge ('SMS_MFA' or 'SOFTWARE_TOKEN_MFA')
    :type mfa_type: ``Optional[str]``
    :param mfa_tokens: Tokens from the MFA exception for continuing authentication
    :type mfa_tokens: ``Optional[dict]``
    """

    def __init__(
        self,
        message: str = "MFA challenge required.",
        mfa_type: str | None = None,
        mfa_tokens: dict | None = None,
    ) -> None:
        """Initialize an MFA challenge required error."""
        super().__init__(message)
        self.mfa_type = mfa_type
        self.mfa_tokens = mfa_tokens or {}


class UnknownError(KwiksetError):
    """Raised when an unknown error occurs."""


class ConnectionError(KwiksetError):  # noqa: A001
    """Raised when a connection to the API cannot be established.

    This exception should be caught by Home Assistant integrations
    to trigger retry logic or mark the device as unavailable.

    :param message: The error message
    :type message: ``str``
    """

    def __init__(self, message: str = "Unable to connect to Kwikset API.") -> None:
        """Initialize a connection error."""
        super().__init__(message)


class TokenExpiredError(AuthenticationError):
    """Raised when the authentication token has expired and cannot be refreshed.

    This exception signals to Home Assistant that re-authentication is required.
    The integration should trigger a config flow reauth step.

    :param message: The error message
    :type message: ``str``
    """

    def __init__(self, message: str = "Authentication token expired.") -> None:
        """Initialize a token expired error."""
        super().__init__(message)


class SubscriptionError(KwiksetError):
    """Define a base error for subscription-related issues."""


class SubscriptionTimeout(SubscriptionError):
    """Raised when a WebSocket connection or acknowledgement times out.

    :param message: The error message
    :type message: ``str``
    """

    def __init__(self, message: str = "Subscription operation timed out.") -> None:
        """Initialize a subscription timeout error."""
        super().__init__(message)


class DeviceError(KwiksetError):
    """Define a base error for device-related issues."""


class InvalidDeviceError(DeviceError):
    """Raised when device data is invalid or missing required fields.

    :param message: The error message
    :type message: ``str``
    :param missing_fields: List of missing required fields
    :type missing_fields: ``Optional[list]``
    """

    def __init__(
        self,
        message: str = "Invalid device data.",
        missing_fields: list | None = None,
    ) -> None:
        """Initialize an invalid device error."""
        super().__init__(message)
        self.missing_fields = missing_fields or []


class InvalidUserError(DeviceError):
    """Raised when user data is invalid or missing required fields.

    :param message: The error message
    :type message: ``str``
    :param missing_fields: List of missing required fields
    :type missing_fields: ``Optional[list]``
    """

    def __init__(
        self,
        message: str = "Invalid user data.",
        missing_fields: list | None = None,
    ) -> None:
        """Initialize an invalid user error."""
        super().__init__(message)
        self.missing_fields = missing_fields or []


class InvalidActionError(DeviceError):
    """Raised when an invalid action is requested.

    :param message: The error message
    :type message: ``str``
    :param action: The invalid action that was attempted
    :type action: ``Optional[str]``
    :param valid_actions: List of valid actions
    :type valid_actions: ``Optional[list]``
    """

    def __init__(
        self,
        message: str = "Invalid action requested.",
        action: str | None = None,
        valid_actions: list | None = None,
    ) -> None:
        """Initialize an invalid action error."""
        super().__init__(message)
        self.action = action
        self.valid_actions = valid_actions or []


class InvalidStatusError(DeviceError):
    """Raised when an invalid status value is provided.

    :param message: The error message
    :type message: ``str``
    :param status: The invalid status value
    :type status: ``Optional[str]``
    :param valid_statuses: List of valid status values
    :type valid_statuses: ``Optional[list]``
    """

    def __init__(
        self,
        message: str = "Invalid status value.",
        status: str | None = None,
        valid_statuses: list | None = None,
    ) -> None:
        """Initialize an invalid status error."""
        super().__init__(message)
        self.status = status
        self.valid_statuses = valid_statuses or []


class DeviceCommandError(DeviceError):
    """Raised when a device command fails.

    :param message: The error message
    :type message: ``str``
    :param device_id: The device identifier
    :type device_id: ``Optional[str]``
    :param command: The command that failed
    :type command: ``Optional[str]``
    """

    def __init__(
        self,
        message: str = "Device command failed.",
        device_id: str | None = None,
        command: str | None = None,
    ) -> None:
        """Initialize a device command error."""
        super().__init__(message)
        self.device_id = device_id
        self.command = command


class MinimumAccessCodeError(DeviceError):
    """Raised when attempting to delete the last access code on a device.

    Kwikset locks require at least one access code to remain on the device
    at all times. This validation mirrors the client-side enforcement in
    the Kwikset mobile app.

    :param message: The error message
    :type message: ``str``
    :param device_id: The device identifier
    :type device_id: ``Optional[str]``
    :param current_count: The current number of access codes on the device
    :type current_count: ``Optional[int]``
    """

    def __init__(
        self,
        message: str = "Cannot delete the last access code. At least one access code is required.",
        device_id: str | None = None,
        current_count: int | None = None,
    ) -> None:
        """Initialize a minimum access code error."""
        super().__init__(message)
        self.device_id = device_id
        self.current_count = current_count
