from __future__ import annotations

from typing import TYPE_CHECKING, Any, NamedTuple

from ..actions import (
    ActionSort,
    ActionsPageResult,
    ActionStatus,
    BoundAction,
    ResourceActionsClient,
)
from ..actions.client import ResourceClientBaseActionsMixin
from ..core import BoundModelBase, Meta, ResourceClientBase
from .domain import (
    CreateZoneResponse,
    CreateZoneRRSetResponse,
    DeleteZoneResponse,
    DeleteZoneRRSetResponse,
    ExportZonefileResponse,
    Zone,
    ZoneAuthoritativeNameservers,
    ZoneMode,
    ZonePrimaryNameserver,
    ZoneRecord,
    ZoneRRSet,
    ZoneRRSetType,
)

if TYPE_CHECKING:
    from .._client import Client

__all__ = [
    "BoundZone",
    "BoundZoneRRSet",
    "ZonesPageResult",
    "ZoneRRSetsPageResult",
    "ZonesClient",
]


class BoundZone(BoundModelBase[Zone], Zone):
    _client: ZonesClient

    model = Zone

    def __init__(
        self,
        client: ZonesClient,
        data: dict[str, Any],
        complete: bool = True,
    ):
        raw = data.get("primary_nameservers")
        if raw is not None:
            data["primary_nameservers"] = [
                ZonePrimaryNameserver.from_dict(o) for o in raw
            ]

        raw = data.get("authoritative_nameservers")
        if raw:
            data["authoritative_nameservers"] = ZoneAuthoritativeNameservers.from_dict(
                raw
            )

        super().__init__(client, data, complete)

    def update(
        self,
        *,
        labels: dict[str, str] | None = None,
    ) -> BoundZone:
        """
        Updates the Zone.

        See https://docs.hetzner.cloud/reference/cloud#zones-update-a-zone

        :param labels: User-defined labels (key/value pairs) for the Resource.
        """
        return self._client.update(self, labels=labels)

    def delete(self) -> DeleteZoneResponse:
        """
        Deletes the Zone.

        See https://docs.hetzner.cloud/reference/cloud#zones-delete-a-zone
        """
        return self._client.delete(self)

    def export_zonefile(self) -> ExportZonefileResponse:
        """
        Returns a generated Zone file in BIND (RFC 1034/1035) format.

        See https://docs.hetzner.cloud/reference/cloud#zones-export-a-zone-file
        """
        return self._client.export_zonefile(self)

    def get_actions_list(
        self,
        *,
        status: list[ActionStatus] | None = None,
        sort: list[ActionSort] | None = None,
        page: int | None = None,
        per_page: int | None = None,
    ) -> ActionsPageResult:
        """
        Returns a paginated list of Actions for a Zone.

        See https://docs.hetzner.cloud/reference/cloud#zones-list-zones

        :param status: Filter the Actions by status.
        :param sort: Sort Actions by field and direction.
        :param page: Page number to get.
        :param per_page: Maximum number of Actions returned per page.
        """
        return self._client.get_actions_list(
            self,
            status=status,
            sort=sort,
            page=page,
            per_page=per_page,
        )

    def get_actions(
        self,
        *,
        status: list[ActionStatus] | None = None,
        sort: list[ActionSort] | None = None,
    ) -> list[BoundAction]:
        """
        Returns all Actions for a Zone.

        See https://docs.hetzner.cloud/reference/cloud#zones-list-zones

        :param status: Filter the Actions by status.
        :param sort: Sort Actions by field and direction.
        """
        return self._client.get_actions(
            self,
            status=status,
            sort=sort,
        )

    def import_zonefile(
        self,
        zonefile: str,
    ) -> BoundAction:
        """
        Imports a zone file, replacing all resource record sets (ZoneRRSet).

        See https://docs.hetzner.cloud/reference/cloud#zone-actions-import-a-zone-file

        :param zonefile: Zone file to import.
        """
        return self._client.import_zonefile(self, zonefile=zonefile)

    def change_protection(
        self,
        *,
        delete: bool | None = None,
    ) -> BoundAction:
        """
        Changes the protection of the Zone.

        See https://docs.hetzner.cloud/reference/cloud#zone-actions-change-a-zones-protection

        :param delete: Prevents the Zone from being deleted.
        """
        return self._client.change_protection(self, delete=delete)

    def change_ttl(
        self,
        ttl: int,
    ) -> BoundAction:
        """
        Changes the TTL of the Zone.

        See https://docs.hetzner.cloud/reference/cloud#zone-actions-change-a-zones-default-ttl

        :param ttl: Default Time To Live (TTL) of the Zone.
        """
        return self._client.change_ttl(self, ttl=ttl)

    def change_primary_nameservers(
        self,
        primary_nameservers: list[ZonePrimaryNameserver],
    ) -> BoundAction:
        """
        Changes the primary nameservers of the Zone.

        See https://docs.hetzner.cloud/reference/cloud#zone-actions-change-a-zones-primary-nameservers

        :param primary_nameservers: Primary nameservers of the Zone.
        """
        return self._client.change_primary_nameservers(
            self,
            primary_nameservers=primary_nameservers,
        )

    def get_rrset(
        self,
        name: str,
        type: ZoneRRSetType,
    ) -> BoundZoneRRSet:
        """
        Returns a single ZoneRRSet from the Zone.

        See https://docs.hetzner.cloud/reference/cloud#zone-rrsets-get-an-rrset

        :param name: Name of the RRSet.
        :param type: Type of the RRSet.
        """
        return self._client.get_rrset(self, name=name, type=type)

    def get_rrset_list(
        self,
        *,
        name: str | None = None,
        type: list[ZoneRRSetType] | None = None,
        label_selector: str | None = None,
        sort: list[str] | None = None,
        page: int | None = None,
        per_page: int | None = None,
    ) -> ZoneRRSetsPageResult:
        """
        Returns all ZoneRRSet in the Zone for a specific page.

        See https://docs.hetzner.cloud/reference/cloud#zone-rrsets-list-rrsets

        :param name: Filter resources by their name. The response will only contain the resources matching exactly the specified name.
        :param type: Filter resources by their type. The response will only contain the resources matching exactly the specified type.
        :param label_selector: Filter resources by labels. The response will only contain resources matching the label selector.
        :param sort: Sort resources by field and direction.
        :param page: Page number to return.
        :param per_page: Maximum number of entries returned per page.
        """
        return self._client.get_rrset_list(
            self,
            name=name,
            type=type,
            label_selector=label_selector,
            sort=sort,
            page=page,
            per_page=per_page,
        )

    def get_rrset_all(
        self,
        *,
        name: str | None = None,
        type: list[ZoneRRSetType] | None = None,
        label_selector: str | None = None,
        sort: list[str] | None = None,
    ) -> list[BoundZoneRRSet]:
        """
        Returns all ZoneRRSet in the Zone.

        See https://docs.hetzner.cloud/reference/cloud#zone-rrsets-list-rrsets

        :param name: Filter resources by their name. The response will only contain the resources matching exactly the specified name.
        :param type: Filter resources by their type. The response will only contain the resources matching exactly the specified type.
        :param label_selector: Filter resources by labels. The response will only contain resources matching the label selector.
        :param sort: Sort resources by field and direction.
        """
        return self._client.get_rrset_all(
            self,
            name=name,
            type=type,
            label_selector=label_selector,
            sort=sort,
        )

    def create_rrset(
        self,
        *,
        name: str,
        type: ZoneRRSetType,
        ttl: int | None = None,
        labels: dict[str, str] | None = None,
        records: list[ZoneRecord] | None = None,
    ) -> CreateZoneRRSetResponse:
        """
        Creates a ZoneRRSet in the Zone.

        See https://docs.hetzner.cloud/reference/cloud#zone-rrsets-create-an-rrset

        :param name: Name of the RRSet.
        :param type: Type of the RRSet.
        :param ttl: Time To Live (TTL) of the RRSet.
        :param labels: User-defined labels (key/value pairs) for the Resource.
        :param records: Records of the RRSet.
        """
        return self._client.create_rrset(
            self,
            name=name,
            type=type,
            ttl=ttl,
            labels=labels,
            records=records,
        )

    def update_rrset(
        self,
        rrset: ZoneRRSet | BoundZoneRRSet,
        *,
        labels: dict[str, str] | None = None,
    ) -> BoundZoneRRSet:
        """
        Updates a ZoneRRSet.

        See https://docs.hetzner.cloud/reference/cloud#zone-rrsets-update-an-rrset

        :param rrset: RRSet to update.
        :param labels: User-defined labels (key/value pairs) for the Resource.
        """
        return self._client.update_rrset(rrset=rrset, labels=labels)

    def delete_rrset(
        self,
        rrset: ZoneRRSet | BoundZoneRRSet,
    ) -> DeleteZoneRRSetResponse:
        """
        Deletes a ZoneRRSet.

        See https://docs.hetzner.cloud/reference/cloud#zone-rrsets-delete-an-rrset

        :param rrset: RRSet to delete.
        """
        return self._client.delete_rrset(rrset=rrset)

    def change_rrset_protection(
        self,
        rrset: ZoneRRSet | BoundZoneRRSet,
        *,
        change: bool | None = None,
    ) -> BoundAction:
        """
        Changes the protection of a ZoneRRSet.

        See https://docs.hetzner.cloud/reference/cloud#zone-rrset-actions-change-an-rrsets-protection

        :param rrset: RRSet to update.
        :param change: Prevent the Zone from being changed (deletion and updates).
        """
        return self._client.change_rrset_protection(rrset=rrset, change=change)

    def change_rrset_ttl(
        self,
        rrset: ZoneRRSet | BoundZoneRRSet,
        ttl: int | None,
    ) -> BoundAction:
        """
        Changes the TTL of a ZoneRRSet.

        See https://docs.hetzner.cloud/reference/cloud#zone-rrset-actions-change-an-rrsets-ttl

        :param rrset: RRSet to update.
        :param change: Time To Live (TTL) of the RRSet.
        """
        return self._client.change_rrset_ttl(rrset=rrset, ttl=ttl)

    def add_rrset_records(
        self,
        rrset: ZoneRRSet | BoundZoneRRSet,
        records: list[ZoneRecord],
        ttl: int | None = None,
    ) -> BoundAction:
        """
        Adds records to a ZoneRRSet.

        See https://docs.hetzner.cloud/reference/cloud#zone-rrset-actions-add-records-to-an-rrset

        :param rrset: RRSet to update.
        :param records: Records to add to the RRSet.
        :param ttl: Time To Live (TTL) of the RRSet.
        """
        return self._client.add_rrset_records(rrset=rrset, records=records, ttl=ttl)

    def update_rrset_records(
        self,
        rrset: ZoneRRSet | BoundZoneRRSet,
        records: list[ZoneRecord],
    ) -> BoundAction:
        """
        Updates records in a ZoneRRSet.

        See https://docs.hetzner.cloud/reference/cloud#zone-rrset-actions-update-records-of-an-rrset

        :param rrset: RRSet to update.
        :param records: Records to update in the RRSet.
        """
        return self._client.update_rrset_records(rrset=rrset, records=records)

    def remove_rrset_records(
        self,
        rrset: ZoneRRSet | BoundZoneRRSet,
        records: list[ZoneRecord],
    ) -> BoundAction:
        """
        Removes records from a ZoneRRSet.

        See https://docs.hetzner.cloud/reference/cloud#zone-rrset-actions-remove-records-from-an-rrset

        :param rrset: RRSet to update.
        :param records: Records to remove from the RRSet.
        """
        return self._client.remove_rrset_records(rrset=rrset, records=records)

    def set_rrset_records(
        self,
        rrset: ZoneRRSet | BoundZoneRRSet,
        records: list[ZoneRecord],
    ) -> BoundAction:
        """
        Sets the records of a ZoneRRSet.

        See https://docs.hetzner.cloud/reference/cloud#zone-rrset-actions-set-records-of-an-rrset

        :param rrset: RRSet to update.
        :param records: Records to set in the RRSet.
        """
        return self._client.set_rrset_records(rrset=rrset, records=records)


class BoundZoneRRSet(BoundModelBase[ZoneRRSet], ZoneRRSet):
    _client: ZonesClient

    model = ZoneRRSet

    def __init__(
        self,
        client: ZonesClient,
        data: dict[str, Any],
        complete: bool = True,
    ):
        raw = data.get("zone")
        if raw is not None:
            data["zone"] = BoundZone(client, data={"id": raw}, complete=False)

        raw = data.get("records")
        if raw is not None:
            data["records"] = [ZoneRecord.from_dict(o) for o in raw]

        super().__init__(client, data, complete)

    def _get_self(self) -> BoundZoneRRSet:
        assert self.data_model.zone is not None
        assert self.data_model.type is not None
        return self._client.get_rrset(
            self.data_model.zone,
            self.data_model.name,
            self.data_model.type,
        )

    def update_rrset(
        self,
        *,
        labels: dict[str, str] | None = None,
    ) -> BoundZoneRRSet:
        """
        Updates the ZoneRRSet.

        See https://docs.hetzner.cloud/reference/cloud#zone-rrsets-update-an-rrset

        :param labels: User-defined labels (key/value pairs) for the Resource.
        """
        return self._client.update_rrset(self, labels=labels)

    def delete_rrset(
        self,
    ) -> DeleteZoneRRSetResponse:
        """
        Deletes the ZoneRRSet.

        See https://docs.hetzner.cloud/reference/cloud#zone-rrsets-delete-an-rrset
        """
        return self._client.delete_rrset(self)

    def change_rrset_protection(
        self,
        *,
        change: bool | None = None,
    ) -> BoundAction:
        """
        Changes the protection of the ZoneRRSet.

        See https://docs.hetzner.cloud/reference/cloud#zone-rrset-actions-change-an-rrsets-protection

        :param change: Prevent the Zone from being changed (deletion and updates).
        """
        return self._client.change_rrset_protection(self, change=change)

    def change_rrset_ttl(
        self,
        ttl: int | None,
    ) -> BoundAction:
        """
        Changes the TTL of the ZoneRRSet.

        See https://docs.hetzner.cloud/reference/cloud#zone-rrset-actions-change-an-rrsets-ttl

        :param change: Time To Live (TTL) of the RRSet.
        """
        return self._client.change_rrset_ttl(self, ttl=ttl)

    def add_rrset_records(
        self,
        records: list[ZoneRecord],
        ttl: int | None = None,
    ) -> BoundAction:
        """
        Adds records to the ZoneRRSet.

        See https://docs.hetzner.cloud/reference/cloud#zone-rrset-actions-add-records-to-an-rrset

        :param records: Records to add to the RRSet.
        :param ttl: Time To Live (TTL) of the RRSet.
        """
        return self._client.add_rrset_records(self, records=records, ttl=ttl)

    def update_rrset_records(
        self,
        records: list[ZoneRecord],
    ) -> BoundAction:
        """
        Updates records in a ZoneRRSet.

        See https://docs.hetzner.cloud/reference/cloud#zone-rrset-actions-update-records-of-an-rrset

        :param records: Records to update in the RRSet.
        """
        return self._client.update_rrset_records(self, records=records)

    def remove_rrset_records(
        self,
        records: list[ZoneRecord],
    ) -> BoundAction:
        """
        Removes records from the ZoneRRSet.

        See https://docs.hetzner.cloud/reference/cloud#zone-rrset-actions-remove-records-from-an-rrset

        :param records: Records to remove from the RRSet.
        """
        return self._client.remove_rrset_records(self, records=records)

    def set_rrset_records(
        self,
        records: list[ZoneRecord],
    ) -> BoundAction:
        """
        Sets the records of the ZoneRRSet.

        See https://docs.hetzner.cloud/reference/cloud#zone-rrset-actions-set-records-of-an-rrset

        :param records: Records to set in the RRSet.
        """
        return self._client.set_rrset_records(self, records=records)


class ZonesPageResult(NamedTuple):
    zones: list[BoundZone]
    meta: Meta


class ZoneRRSetsPageResult(NamedTuple):
    rrsets: list[BoundZoneRRSet]
    meta: Meta


class ZonesClient(
    ResourceClientBaseActionsMixin,
    ResourceClientBase,
):
    """
    ZoneClient is a client for the Zone (DNS) API.

    See https://docs.hetzner.cloud/reference/cloud#zones and https://docs.hetzner.cloud/reference/cloud#zone-rrsets.
    """

    _base_url = "/zones"

    actions: ResourceActionsClient
    """Zones scoped actions client

    :type: :class:`ResourceActionsClient <hcloud.actions.client.ResourceActionsClient>`
    """

    def __init__(self, client: Client):
        super().__init__(client)
        self.actions = ResourceActionsClient(client, self._base_url)

    def get(self, id_or_name: int | str) -> BoundZone:
        """
        Returns a single Zone.

        See https://docs.hetzner.cloud/reference/cloud#zones-get-a-zone

        :param id_or_name: ID or Name of the Zone.
        """
        response = self._client.request(
            method="GET",
            url=f"{self._base_url}/{id_or_name}",
        )
        return BoundZone(self, response["zone"])

    def get_list(
        self,
        *,
        name: str | None = None,
        mode: ZoneMode | None = None,
        label_selector: str | None = None,
        sort: list[str] | None = None,
        page: int | None = None,
        per_page: int | None = None,
    ) -> ZonesPageResult:
        """
        Returns a list of Zone for a specific page.

        See https://docs.hetzner.cloud/reference/cloud#zones-list-zones

        :param name: Filter resources by their name. The response will only contain the resources matching exactly the specified name.
        :param mode: Filter resources by their mode. The response will only contain the resources matching exactly the specified mode.
        :param label_selector: Filter resources by labels. The response will only contain resources matching the label selector.
        :param sort: Sort resources by field and direction.
        :param page: Page number to return.
        :param per_page: Maximum number of entries returned per page.
        """
        params: dict[str, Any] = {}
        if name is not None:
            params["name"] = name
        if mode is not None:
            params["mode"] = mode
        if label_selector is not None:
            params["label_selector"] = label_selector
        if sort is not None:
            params["sort"] = sort
        if page is not None:
            params["page"] = page
        if per_page is not None:
            params["per_page"] = per_page

        response = self._client.request(
            method="GET",
            url=f"{self._base_url}",
            params=params,
        )
        return ZonesPageResult(
            zones=[BoundZone(self, item) for item in response["zones"]],
            meta=Meta.parse_meta(response),
        )

    def get_all(
        self,
        *,
        name: str | None = None,
        mode: ZoneMode | None = None,
        label_selector: str | None = None,
        sort: list[str] | None = None,
    ) -> list[BoundZone]:
        """
        Returns a list of all Zone.

        See https://docs.hetzner.cloud/reference/cloud#zones-list-zones

        :param name: Filter resources by their name. The response will only contain the resources matching exactly the specified name.
        :param mode: Filter resources by their mode. The response will only contain the resources matching exactly the specified mode.
        :param label_selector: Filter resources by labels. The response will only contain resources matching the label selector.
        :param sort: Sort resources by field and direction.
        """
        return self._iter_pages(
            self.get_list,
            name=name,
            mode=mode,
            label_selector=label_selector,
            sort=sort,
        )

    def create(
        self,
        *,
        name: str,
        mode: ZoneMode,
        ttl: int | None = None,
        labels: dict[str, str] | None = None,
        primary_nameservers: list[ZonePrimaryNameserver] | None = None,
        rrsets: list[ZoneRRSet] | None = None,
        zonefile: str | None = None,
    ) -> CreateZoneResponse:
        """
        Creates a Zone.

        A default SOA and three NS resource records with the assigned Hetzner nameservers are created automatically.

        See https://docs.hetzner.cloud/reference/cloud#zones-create-a-zone

        :param name: Name of the Zone.
        :param mode: Mode of the Zone.
        :param ttl: Default Time To Live (TTL) of the Zone.
        :param labels: User-defined labels (key/value pairs) for the Resource.
        :param primary_nameservers: Primary nameservers of the Zone.
        :param rrsets: RRSets to be added to the Zone.
        :param zonefile: Zone file to import.
        """
        data: dict[str, Any] = {
            "name": name,
            "mode": mode,
        }
        if ttl is not None:
            data["ttl"] = ttl
        if labels is not None:
            data["labels"] = labels
        if primary_nameservers is not None:
            data["primary_nameservers"] = [o.to_payload() for o in primary_nameservers]
        if rrsets is not None:
            data["rrsets"] = [o.to_payload() for o in rrsets]
        if zonefile is not None:
            data["zonefile"] = zonefile

        response = self._client.request(
            method="POST",
            url=f"{self._base_url}",
            json=data,
        )

        return CreateZoneResponse(
            zone=BoundZone(self, response["zone"]),
            action=BoundAction(self._parent.actions, response["action"]),
        )

    def update(
        self,
        zone: Zone | BoundZone,
        *,
        labels: dict[str, str] | None = None,
    ) -> BoundZone:
        """
        Updates a Zone.

        See https://docs.hetzner.cloud/reference/cloud#zones-update-a-zone

        :param zone: Zone to update.
        :param labels: User-defined labels (key/value pairs) for the Resource.
        """
        data: dict[str, Any] = {}
        if labels is not None:
            data["labels"] = labels

        response = self._client.request(
            method="PUT",
            url=f"{self._base_url}/{zone.id_or_name}",
            json=data,
        )
        return BoundZone(self, response["zone"])

    def delete(
        self,
        zone: Zone | BoundZone,
    ) -> DeleteZoneResponse:
        """
        Deletes a Zone.

        See https://docs.hetzner.cloud/reference/cloud#zones-delete-a-zone

        :param zone: Zone to delete.
        """
        response = self._client.request(
            method="DELETE",
            url=f"{self._base_url}/{zone.id_or_name}",
        )

        return DeleteZoneResponse(
            action=BoundAction(self._parent.actions, response["action"]),
        )

    def export_zonefile(
        self,
        zone: Zone | BoundZone,
    ) -> ExportZonefileResponse:
        """
        Returns a generated Zone file in BIND (RFC 1034/1035) format.

        See https://docs.hetzner.cloud/reference/cloud#zones-export-a-zone-file

        :param zone: Zone to export the zone file from.
        """
        response = self._client.request(
            method="GET",
            url=f"{self._base_url}/{zone.id_or_name}/zonefile",
        )
        return ExportZonefileResponse(response["zonefile"])

    def get_actions_list(
        self,
        zone: Zone | BoundZone,
        *,
        status: list[ActionStatus] | None = None,
        sort: list[ActionSort] | None = None,
        page: int | None = None,
        per_page: int | None = None,
    ) -> ActionsPageResult:
        """
        Returns a paginated list of Actions for a Zone.

        See https://docs.hetzner.cloud/reference/cloud#zones-list-zones

        :param zone: Zone to get the Actions for.
        :param status: Filter the Actions by status.
        :param sort: Sort Actions by field and direction.
        :param page: Page number to get.
        :param per_page: Maximum number of Actions returned per page.
        """
        return self._get_actions_list(
            f"{self._base_url}/{zone.id_or_name}",
            status=status,
            sort=sort,
            page=page,
            per_page=per_page,
        )

    def get_actions(
        self,
        zone: Zone | BoundZone,
        *,
        status: list[ActionStatus] | None = None,
        sort: list[ActionSort] | None = None,
    ) -> list[BoundAction]:
        """
        Returns all Actions for a Zone.

        See https://docs.hetzner.cloud/reference/cloud#zones-list-zones

        :param zone: Zone to get the Actions for.
        :param status: Filter the Actions by status.
        :param sort: Sort Actions by field and direction.
        """
        return self._iter_pages(
            self.get_actions_list,
            zone,
            status=status,
            sort=sort,
        )

    def import_zonefile(
        self,
        zone: Zone | BoundZone,
        zonefile: str,
    ) -> BoundAction:
        """
        Imports a zone file, replacing all resource record sets (ZoneRRSet).

        See https://docs.hetzner.cloud/reference/cloud#zone-actions-import-a-zone-file

        :param zone: Zone to import the zone file into.
        :param zonefile: Zone file to import.
        """
        data: dict[str, Any] = {
            "zonefile": zonefile,
        }

        response = self._client.request(
            method="POST",
            url=f"{self._base_url}/{zone.id_or_name}/actions/import_zonefile",
            json=data,
        )
        return BoundAction(self._parent.actions, response["action"])

    def change_protection(
        self,
        zone: Zone | BoundZone,
        *,
        delete: bool | None = None,
    ) -> BoundAction:
        """
        Changes the protection of a Zone.

        See https://docs.hetzner.cloud/reference/cloud#zone-actions-change-a-zones-protection

        :param zone: Zone to update.
        :param delete: Prevents the Zone from being deleted.
        """
        data: dict[str, Any] = {}
        if delete is not None:
            data["delete"] = delete

        response = self._client.request(
            method="POST",
            url=f"{self._base_url}/{zone.id_or_name}/actions/change_protection",
            json=data,
        )
        return BoundAction(self._parent.actions, response["action"])

    def change_ttl(
        self,
        zone: Zone | BoundZone,
        ttl: int,
    ) -> BoundAction:
        """
        Changes the TTL of a Zone.

        See https://docs.hetzner.cloud/reference/cloud#zone-actions-change-a-zones-default-ttl

        :param zone: Zone to update.
        :param ttl: Default Time To Live (TTL) of the Zone.
        """
        data: dict[str, Any] = {
            "ttl": ttl,
        }

        response = self._client.request(
            method="POST",
            url=f"{self._base_url}/{zone.id_or_name}/actions/change_ttl",
            json=data,
        )
        return BoundAction(self._parent.actions, response["action"])

    def change_primary_nameservers(
        self,
        zone: Zone | BoundZone,
        primary_nameservers: list[ZonePrimaryNameserver],
    ) -> BoundAction:
        """
        Changes the primary nameservers of a Zone.

        See https://docs.hetzner.cloud/reference/cloud#zone-actions-change-a-zones-primary-nameservers

        :param zone: Zone to update.
        :param primary_nameservers: Primary nameservers of the Zone.
        """
        data: dict[str, Any] = {
            "primary_nameservers": [o.to_payload() for o in primary_nameservers],
        }

        response = self._client.request(
            method="POST",
            url=f"{self._base_url}/{zone.id_or_name}/actions/change_primary_nameservers",
            json=data,
        )
        return BoundAction(self._parent.actions, response["action"])

    def get_rrset(
        self,
        zone: Zone | BoundZone,
        name: str,
        type: ZoneRRSetType,
    ) -> BoundZoneRRSet:
        """
        Returns a single ZoneRRSet from the Zone.

        See https://docs.hetzner.cloud/reference/cloud#zone-rrsets-get-an-rrset

        :param zone: Zone to fetch the RRSet from.
        :param name: Name of the RRSet.
        :param type: Type of the RRSet.
        """
        response = self._client.request(
            method="GET",
            url=f"{self._base_url}/{zone.id_or_name}/rrsets/{name}/{type}",
        )
        return BoundZoneRRSet(self, response["rrset"])

    def get_rrset_list(
        self,
        zone: Zone | BoundZone,
        *,
        name: str | None = None,
        type: list[ZoneRRSetType] | None = None,
        label_selector: str | None = None,
        sort: list[str] | None = None,
        page: int | None = None,
        per_page: int | None = None,
    ) -> ZoneRRSetsPageResult:
        """
        Returns all ZoneRRSet in the Zone for a specific page.

        See https://docs.hetzner.cloud/reference/cloud#zone-rrsets-list-rrsets

        :param zone: Zone to fetch the RRSets from.
        :param name: Filter resources by their name. The response will only contain the resources matching exactly the specified name.
        :param type: Filter resources by their type. The response will only contain the resources matching exactly the specified type.
        :param label_selector: Filter resources by labels. The response will only contain resources matching the label selector.
        :param sort: Sort resources by field and direction.
        :param page: Page number to return.
        :param per_page: Maximum number of entries returned per page.
        """
        params: dict[str, Any] = {}
        if name is not None:
            params["name"] = name
        if type is not None:
            params["type"] = type
        if label_selector is not None:
            params["label_selector"] = label_selector
        if sort is not None:
            params["sort"] = sort
        if page is not None:
            params["page"] = page
        if per_page is not None:
            params["per_page"] = per_page

        response = self._client.request(
            method="GET",
            url=f"{self._base_url}/{zone.id_or_name}/rrsets",
            params=params,
        )
        return ZoneRRSetsPageResult(
            rrsets=[BoundZoneRRSet(self, item) for item in response["rrsets"]],
            meta=Meta.parse_meta(response),
        )

    def get_rrset_all(
        self,
        zone: Zone | BoundZone,
        *,
        name: str | None = None,
        type: list[ZoneRRSetType] | None = None,
        label_selector: str | None = None,
        sort: list[str] | None = None,
    ) -> list[BoundZoneRRSet]:
        """
        Returns all ZoneRRSet in the Zone.

        See https://docs.hetzner.cloud/reference/cloud#zone-rrsets-list-rrsets

        :param zone: Zone to fetch the RRSets from.
        :param name: Filter resources by their name. The response will only contain the resources matching exactly the specified name.
        :param type: Filter resources by their type. The response will only contain the resources matching exactly the specified type.
        :param label_selector: Filter resources by labels. The response will only contain resources matching the label selector.
        :param sort: Sort resources by field and direction.
        """
        return self._iter_pages(
            self.get_rrset_list,
            zone,
            name=name,
            type=type,
            label_selector=label_selector,
            sort=sort,
        )

    def create_rrset(
        self,
        zone: Zone | BoundZone,
        *,
        name: str,
        type: ZoneRRSetType,
        ttl: int | None = None,
        labels: dict[str, str] | None = None,
        records: list[ZoneRecord] | None = None,
    ) -> CreateZoneRRSetResponse:
        """
        Creates a ZoneRRSet in the Zone.

        See https://docs.hetzner.cloud/reference/cloud#zone-rrsets-create-an-rrset

        :param zone: Zone to create the RRSets in.
        :param name: Name of the RRSet.
        :param type: Type of the RRSet.
        :param ttl: Time To Live (TTL) of the RRSet.
        :param labels: User-defined labels (key/value pairs) for the Resource.
        :param records: Records of the RRSet.
        """
        data: dict[str, Any] = {
            "name": name,
            "type": type,
        }
        if ttl is not None:
            data["ttl"] = ttl
        if labels is not None:
            data["labels"] = labels
        if records is not None:
            data["records"] = [o.to_payload() for o in records]

        response = self._client.request(
            method="POST",
            url=f"{self._base_url}/{zone.id_or_name}/rrsets",
            json=data,
        )
        return CreateZoneRRSetResponse(
            rrset=BoundZoneRRSet(self, response["rrset"]),
            action=BoundAction(self._parent.actions, response["action"]),
        )

    def update_rrset(
        self,
        rrset: ZoneRRSet | BoundZoneRRSet,
        *,
        labels: dict[str, str] | None = None,
    ) -> BoundZoneRRSet:
        """
        Updates a ZoneRRSet.

        See https://docs.hetzner.cloud/reference/cloud#zone-rrsets-update-an-rrset

        :param rrset: RRSet to update.
        :param labels: User-defined labels (key/value pairs) for the Resource.
        """
        if rrset.zone is None:
            raise ValueError("rrset zone property is none")

        data: dict[str, Any] = {}
        if labels is not None:
            data["labels"] = labels

        response = self._client.request(
            method="PUT",
            url=f"{self._base_url}/{rrset.zone.id_or_name}/rrsets/{rrset.name}/{rrset.type}",
            json=data,
        )
        return BoundZoneRRSet(self, response["rrset"])

    def delete_rrset(
        self,
        rrset: ZoneRRSet | BoundZoneRRSet,
    ) -> DeleteZoneRRSetResponse:
        """
        Deletes a ZoneRRSet.

        See https://docs.hetzner.cloud/reference/cloud#zone-rrsets-delete-an-rrset

        :param rrset: RRSet to delete.
        """
        if rrset.zone is None:
            raise ValueError("rrset zone property is none")

        response = self._client.request(
            method="DELETE",
            url=f"{self._base_url}/{rrset.zone.id_or_name}/rrsets/{rrset.name}/{rrset.type}",
        )
        return DeleteZoneRRSetResponse(
            action=BoundAction(self._parent.actions, response["action"]),
        )

    def change_rrset_protection(
        self,
        rrset: ZoneRRSet | BoundZoneRRSet,
        *,
        change: bool | None = None,
    ) -> BoundAction:
        """
        Changes the protection of a ZoneRRSet.

        See https://docs.hetzner.cloud/reference/cloud#zone-rrset-actions-change-an-rrsets-protection

        :param rrset: RRSet to update.
        :param change: Prevent the Zone from being changed (deletion and updates).
        """
        if rrset.zone is None:
            raise ValueError("rrset zone property is none")

        data: dict[str, Any] = {}
        if change is not None:
            data["change"] = change

        response = self._client.request(
            method="POST",
            url=f"{self._base_url}/{rrset.zone.id_or_name}/rrsets/{rrset.name}/{rrset.type}/actions/change_protection",
            json=data,
        )
        return BoundAction(self._parent.actions, response["action"])

    def change_rrset_ttl(
        self,
        rrset: ZoneRRSet | BoundZoneRRSet,
        ttl: int | None,
    ) -> BoundAction:
        """
        Changes the TTL of a ZoneRRSet.

        See https://docs.hetzner.cloud/reference/cloud#zone-rrset-actions-change-an-rrsets-ttl

        :param rrset: RRSet to update.
        :param change: Time To Live (TTL) of the RRSet.
        """
        if rrset.zone is None:
            raise ValueError("rrset zone property is none")

        data: dict[str, Any] = {
            "ttl": ttl,
        }

        response = self._client.request(
            method="POST",
            url=f"{self._base_url}/{rrset.zone.id_or_name}/rrsets/{rrset.name}/{rrset.type}/actions/change_ttl",
            json=data,
        )
        return BoundAction(self._parent.actions, response["action"])

    def add_rrset_records(
        self,
        rrset: ZoneRRSet | BoundZoneRRSet,
        records: list[ZoneRecord],
        ttl: int | None = None,
    ) -> BoundAction:
        """
        Adds records to a ZoneRRSet.

        See https://docs.hetzner.cloud/reference/cloud#zone-rrset-actions-add-records-to-an-rrset

        :param rrset: RRSet to update.
        :param records: Records to add to the RRSet.
        :param ttl: Time To Live (TTL) of the RRSet.
        """
        if rrset.zone is None:
            raise ValueError("rrset zone property is none")

        data: dict[str, Any] = {
            "records": [o.to_payload() for o in records],
        }
        if ttl is not None:
            data["ttl"] = ttl

        response = self._client.request(
            method="POST",
            url=f"{self._base_url}/{rrset.zone.id_or_name}/rrsets/{rrset.name}/{rrset.type}/actions/add_records",
            json=data,
        )
        return BoundAction(self._parent.actions, response["action"])

    def update_rrset_records(
        self,
        rrset: ZoneRRSet | BoundZoneRRSet,
        records: list[ZoneRecord],
    ) -> BoundAction:
        """
        Updates records in a ZoneRRSet.

        See https://docs.hetzner.cloud/reference/cloud#zone-rrset-actions-update-records-of-an-rrset

        :param rrset: RRSet to update.
        :param records: Records to update in the RRSet.
        """
        if rrset.zone is None:
            raise ValueError("rrset zone property is none")

        data: dict[str, Any] = {
            "records": [o.to_payload() for o in records],
        }

        response = self._client.request(
            method="POST",
            url=f"{self._base_url}/{rrset.zone.id_or_name}/rrsets/{rrset.name}/{rrset.type}/actions/update_records",
            json=data,
        )
        return BoundAction(self._parent.actions, response["action"])

    def remove_rrset_records(
        self,
        rrset: ZoneRRSet | BoundZoneRRSet,
        records: list[ZoneRecord],
    ) -> BoundAction:
        """
        Removes records from a ZoneRRSet.

        See https://docs.hetzner.cloud/reference/cloud#zone-rrset-actions-remove-records-from-an-rrset

        :param rrset: RRSet to update.
        :param records: Records to remove from the RRSet.
        """
        if rrset.zone is None:
            raise ValueError("rrset zone property is none")

        data: dict[str, Any] = {
            "records": [o.to_payload() for o in records],
        }

        response = self._client.request(
            method="POST",
            url=f"{self._base_url}/{rrset.zone.id_or_name}/rrsets/{rrset.name}/{rrset.type}/actions/remove_records",
            json=data,
        )
        return BoundAction(self._parent.actions, response["action"])

    def set_rrset_records(
        self,
        rrset: ZoneRRSet | BoundZoneRRSet,
        records: list[ZoneRecord],
    ) -> BoundAction:
        """
        Sets the records of a ZoneRRSet.

        See https://docs.hetzner.cloud/reference/cloud#zone-rrset-actions-set-records-of-an-rrset

        :param rrset: RRSet to update.
        :param records: Records to set in the RRSet.
        """
        if rrset.zone is None:
            raise ValueError("rrset zone property is none")

        data: dict[str, Any] = {
            "records": [o.to_payload() for o in records],
        }

        response = self._client.request(
            method="POST",
            url=f"{self._base_url}/{rrset.zone.id_or_name}/rrsets/{rrset.name}/{rrset.type}/actions/set_records",
            json=data,
        )
        return BoundAction(self._parent.actions, response["action"])
