"""Type definitions for aiontfy."""

from dataclasses import dataclass, field
from datetime import UTC, datetime
from enum import IntEnum, StrEnum

from mashumaro import field_options
from mashumaro.mixins.orjson import DataClassORJSONMixin
from yarl import URL

from .const import MAX_PRIORITY, MIN_PRIORITY


class DeleteAfter(IntEnum):
    """Delete after periods."""

    NEVER = 0
    AFTER_THREE_HRS = 10800
    AFTER_ONE_DAY = 86400
    AFTER_ONE_WEEK = 604800
    AFTER_ONE_MONTH = 2592000


class Priority(IntEnum):
    """Message priority."""

    MIN = 1
    LOW = 2
    DEFAULT = 3
    HIGH = 4
    MAX = 5


class Sound(StrEnum):
    """Notification sound."""

    NO_SOUND = "none"
    DING = "ding"
    JUNTOS = "juntos"
    PRISTINE = "pristine"
    DADUM = "dadum"
    POP = "pop"
    POP_SWOOSH = "pop-swoosh"
    BEEP = "beep"


class Everyone(StrEnum):
    """Everyone access."""

    DENY = "deny-all"
    READ = "read-only"
    WRITE = "write-only"
    READ_WRITE = "read-write"


@dataclass(kw_only=True, frozen=True)
class HttpAction(DataClassORJSONMixin):
    """An Http ntfy action.

    Attributes
    ----------
    label : str
        Label of the action button in the notification.
    url : URL
        URL to which the HTTP request will be sent.
    method : str, optional
        HTTP method to use for request, default is POST.
    headers : dict[str, str] or None, optional
        HTTP headers to pass in request.
    body : str or None, optional
        HTTP body.
    clear : bool, optional
        Clear notification after HTTP request succeeds. If the request fails, the notification is not cleared.
    """

    action: str = field(default="http", init=False)
    label: str
    url: URL = field(metadata=field_options(serialize=str, deserialize=URL))
    method: str = "POST"
    headers: dict[str, str] | None = None
    body: str | None = None
    clear: bool = False


@dataclass(kw_only=True, frozen=True)
class BroadcastAction(DataClassORJSONMixin):
    """A broadcast ntfy action.

    Attributes
    ----------
    label : str
        Label of the action button in the notification.
    intent : str or None, optional
        Android intent name, default is io.heckel.ntfy.USER_ACTION.
    extras : dict[str, str] or None, optional
        Android intent extras. Currently, only string extras are supported.
    clear : bool, optional
        Clear notification after action button is tapped.
    """

    action: str = field(default="broadcast", init=False)
    label: str
    intent: str | None = None
    extras: dict[str, str] | None = None
    clear: bool = False


@dataclass(kw_only=True, frozen=True)
class ViewAction(DataClassORJSONMixin):
    """A view ntfy action.

    Attributes
    ----------
    label : str
        Label of the action button in the notification.
    url : URL
        URL to open when action is tapped.
    clear : bool, optional
        Clear notification after action button is tapped.
    """

    action: str = field(default="view", init=False)
    label: str
    url: URL = field(metadata=field_options(serialize=str, deserialize=URL))
    clear: bool = False


@dataclass(kw_only=True, frozen=True)
class CopyAction(DataClassORJSONMixin):
    """A copy ntfy action.

    Attributes
    ----------
    label : str
        Label of the action button in the notification.
    value : Value
        Value to copy to the clipboard when action is tapped.
    clear : bool, optional
        Clear notification after action button is tapped.
    """

    action: str = field(default="copy", init=False)
    label: str
    value: str
    clear: bool = False


@dataclass(kw_only=True, frozen=True)
class Message(DataClassORJSONMixin):
    """A message to publish to ntfy.

    Attributes
    ----------
    topic : str
        Target topic name.
    message : str or None, optional
        Message body; set to triggered if empty or not passed.
    title : str or None, optional
        Message title. Defaults to the topic short URL (ntfy.sh/mytopic) if not set.
    tags : list[str], optional
        List of tags that may or not map to emojis (https://docs.ntfy.sh/emojis/).
    priority : int or None, optional
        Message priority with 1=min, 3=default and 5=max
    actions : list[ViewAction or BroadcastAction or HttpAction or CopyAction], optional
        Custom user action buttons for notifications.
    click : URL or None, optional
        Website opened when notification is clicked.
    attach : URL or None, optional
        URL of an attachment.
    markdown : bool, optional
        Set to true if the message is Markdown-formatted.
    icon : URL or None, optional
        URL to use as notification icon.
    filename : str or None, optional
        File name of the attachment.
    delay : str or None, optional
        Timestamp or duration for delayed delivery.
    email : str or None, optional
        E-mail address for e-mail notifications.
    call : str or None, optional
        Phone number to use for voice call.
    sequence_id: str or None, optional
        Sequence ID for updating/deleting notifications

    """

    topic: str
    message: str | None = None
    title: str | None = None
    tags: list[str] = field(default_factory=list)
    priority: int | None = None
    actions: list[ViewAction | BroadcastAction | HttpAction | CopyAction] = field(
        default_factory=list
    )
    click: URL | None = field(
        default=None, metadata=field_options(serialize=str, deserialize=URL)
    )
    attach: URL | None = field(
        default=None, metadata=field_options(serialize=str, deserialize=URL)
    )
    markdown: bool = False
    icon: URL | None = field(
        default=None, metadata=field_options(serialize=str, deserialize=URL)
    )
    filename: str | None = None
    delay: str | None = None
    email: str | None = None
    call: str | None = None
    sequence_id: str | None = None

    def __post_init__(self) -> None:
        """Post-initialization processing to validate attributes.

        Raises
        ------
        ValueError
            If the priority is not between the minimum and maximum allowed values.

        """

        if self.priority is not None and (
            self.priority < MIN_PRIORITY or self.priority > MAX_PRIORITY
        ):
            msg = f"Priority must be between {MIN_PRIORITY} and {MAX_PRIORITY}"
            raise ValueError(msg)

    def to_x_headers(self) -> dict[str, str]:
        """Serialize the dataclass as a dict with X- keys (e.g., X-Message, X-Title)."""
        mapping = {
            "message": "X-Message",
            "title": "X-Title",
            "tags": "X-Tags",
            "priority": "X-Priority",
            "actions": "X-Actions",
            "click": "X-Click",
            "attach": "X-Attach",
            "markdown": "X-Markdown",
            "icon": "X-Icon",
            "filename": "X-Filename",
            "delay": "X-Delay",
            "email": "X-Email",
            "call": "X-Call",
            "sequence_id": "X-Sequence-ID",
        }
        data = self.to_dict()

        result = {}
        for key, value in data.items():
            if value is None or value == [] or key not in mapping:
                continue
            if key == "actions":
                actions = []
                for action in value:
                    params = []
                    for action_key, action_value in action.items():
                        if action_value is None:
                            continue
                        if isinstance(action_value, bool):
                            params.append(f"{action_key}={int(action_value)}")
                        elif isinstance(action_value, dict):
                            params.extend(
                                f"{action_key}.{subkey}={subvalue!r}"
                                for subkey, subvalue in action_value.items()
                            )
                        else:
                            params.append(f"{action_key}={str(action_value)!r}")
                    actions.append(", ".join(params))
                result[mapping[key]] = "; ".join(actions)
            elif isinstance(value, list):
                result[mapping[key]] = ",".join(v for v in value)
            elif isinstance(value, bool):
                result[mapping[key]] = str(int(value))
            else:
                result[mapping[key]] = (
                    str(value)
                    .replace("\\", "\\\\")
                    .replace("\r", "\\r")
                    .replace("\n", "\\n")
                )

        return result


class Event(StrEnum):
    """Message type."""

    OPEN = "open"
    KEEPALIVE = "keepalive"
    MESSAGE = "message"
    POLL_REQUEST = "poll_request"
    MESSAGE_CLEAR = "message_clear"
    MESSAGE_DELETE = "message_delete"


def timestamp(ts: int) -> datetime:
    """Serialize timestamp to datetime."""
    return datetime.fromtimestamp(ts, tz=UTC)


@dataclass(kw_only=True, frozen=True)
class Attachment(DataClassORJSONMixin):
    """Details about an attachment."""

    name: str
    url: URL = field(metadata=field_options(serialize=str, deserialize=URL))
    type: str | None = None
    size: int | None = None
    expires: datetime | None = field(
        default=None, metadata=field_options(deserialize=timestamp)
    )


@dataclass(kw_only=True, frozen=True)
class Notification(DataClassORJSONMixin):
    """A notification received from a subscribed topic."""

    id: str
    time: datetime = field(metadata=field_options(deserialize=timestamp))
    expires: datetime | None = field(
        default=None, metadata=field_options(deserialize=timestamp)
    )
    event: Event
    topic: str
    message: str | None = None
    title: str | None = None
    tags: list[str] = field(default_factory=list)
    priority: Priority | None = None
    click: URL | None = field(
        default=None, metadata=field_options(serialize=str, deserialize=URL)
    )
    icon: URL | None = field(
        default=None, metadata=field_options(serialize=str, deserialize=URL)
    )
    actions: list[ViewAction | BroadcastAction | HttpAction] = field(
        default_factory=list
    )
    attachment: Attachment | None = None
    content_type: str | None = None
    sequence_id: str | None = None


@dataclass(kw_only=True, frozen=True)
class Stats(DataClassORJSONMixin):
    """Stats response.

    Attributes
    ----------
    messages : int
        The total number of messages.
    messages_rate : float
        Average number of messages per second.
    """

    messages: int
    messages_rate: float


@dataclass(kw_only=True, frozen=True)
class Version(DataClassORJSONMixin):
    """Version response."""

    version: str
    commit: str
    date: str


@dataclass(kw_only=True, frozen=True)
class Subscription(DataClassORJSONMixin):
    """Subscription information."""

    base_url: URL = field(metadata=field_options(serialize=str, deserialize=URL))
    topic: str
    display_name: str


@dataclass(kw_only=True, frozen=True)
class NotificationPrefs(DataClassORJSONMixin):
    """Notification preferences."""

    sound: Sound | None = None
    min_priority: Priority | None = None
    delete_after: DeleteAfter | None = None


@dataclass(kw_only=True, frozen=True)
class AccountTokenResponse(DataClassORJSONMixin):
    """Account token response."""

    token: str
    label: str | None = None
    last_access: datetime | None = field(
        default=None, metadata=field_options(deserialize=timestamp)
    )
    last_origin: str | None = None
    expires: datetime | None = field(
        default=None, metadata=field_options(deserialize=timestamp)
    )


@dataclass(kw_only=True, frozen=True)
class AccountTier(DataClassORJSONMixin):
    """Account tear information."""

    code: str
    name: str


@dataclass(kw_only=True, frozen=True)
class AccountLimits(DataClassORJSONMixin):
    """Account limits information."""

    basis: str | None = None
    messages: int
    messages_expiry_duration: int
    emails: int
    calls: int
    reservations: int
    attachment_total_size: int
    attachment_file_size: int
    attachment_expiry_duration: int
    attachment_bandwidth: int


@dataclass(kw_only=True, frozen=True)
class AccountStats(DataClassORJSONMixin):
    """Account stats."""

    messages: int
    messages_remaining: int
    emails: int
    emails_remaining: int
    calls: int
    calls_remaining: int
    reservations: int
    reservations_remaining: int
    attachment_total_size: int
    attachment_total_size_remaining: int


@dataclass(kw_only=True, frozen=True)
class Reservation(DataClassORJSONMixin):
    """Topic reservation settings."""

    topic: str
    everyone: str


@dataclass(kw_only=True, frozen=True)
class AccountBilling(DataClassORJSONMixin):
    """Acount billing information."""

    customer: bool
    subscription: bool
    status: str | None = None
    interval: str | None = None
    paid_until: datetime = field(metadata=field_options(deserialize=timestamp))
    cancel_at: datetime | None = field(
        default=None, metadata=field_options(deserialize=timestamp)
    )


@dataclass(kw_only=True, frozen=True)
class Account(DataClassORJSONMixin):
    """Account response."""

    username: str
    role: str | None = None
    sync_topic: str | None = None
    language: str | None = None
    notification: NotificationPrefs | None = None
    subscriptions: list[Subscription] = field(default_factory=list)
    reservations: list[Reservation] = field(default_factory=list)
    tokens: list[AccountTokenResponse] = field(default_factory=list)
    tier: AccountTier | None = None
    limits: AccountLimits | None = None
    stats: AccountStats
    billing: AccountBilling | None = None


@dataclass(kw_only=True, frozen=True)
class Response(DataClassORJSONMixin):
    """Success response."""

    success: bool
