import abc
from typing import Any, Optional

from typing_extensions import override

from ._base import NotchPayBaseResource, AsyncNotchPayBaseResource
from .exceptions import NotchPayError
from .schemas import NotchPayResponseList, NotchPayResponse
from .schemas.base import MaybeAwaitable
from .schemas.beneficiaries import (
    NotchPayBeneficiary,
    NotchPayBeneficiaryCreate,
    NotchPayBeneficiaryUpdate,
    NotchPayBeneficiaryResponse,
)

__all__ = [
    "NotchPayBeneficiaries",
    "AsyncNotchPayBeneficiaries",
]


class _NotchPayBeneficiaries(abc.ABC):
    """Abstract base class for Notchpay beneficiaries management."""

    @abc.abstractmethod
    def list(
            self,
            limit: int = 30,
            page: int = 1,
            search: Optional[str] = None,
            country: Optional[str] = None,
            type: Optional[str] = None,
    ) -> MaybeAwaitable[NotchPayResponseList[NotchPayBeneficiary]]:
        """List all beneficiaries.

        Args:
            limit: Number of items per page (default: 30).
            page: Page number (default: 1).
            search: Search by name, email or phone.
            country: Filter by country code.
            type: Filter by beneficiary type.

        Returns:
            NotchPayResponseList[NotchPayBeneficiary]: Paginated list of beneficiaries.

        Raises:
            NotchPayError: In case of API request error.

        References:
            https://developer.notchpay.co/api-reference/list-all-beneficiaries

        Example:
            >>> from notchpay import NotchPay
            >>> notchpay = NotchPay("your_api_key")
            >>> beneficiaries = notchpay.beneficiaries.list(limit=10)
            >>> for beneficiary in beneficiaries.items:
            ...     print(beneficiary.id, beneficiary.name)
        """
        ...

    @abc.abstractmethod
    def create(self, data: NotchPayBeneficiaryCreate) -> MaybeAwaitable[NotchPayBeneficiaryResponse]:
        """Create a new beneficiary.

        Args:
            data: Beneficiary creation data.

        Returns:
            NotchPayBeneficiaryResponse: Beneficiary creation response.

        Raises:
            NotchPayError: In case of creation error.

        References:
            https://developer.notchpay.co/api-reference/create-a-beneficiary

        Example:
            >>> from notchpay import NotchPay
            >>> notchpay = NotchPay("your_api_key")
            >>> beneficiary = notchpay.beneficiaries.create(
            ...     NotchPayBeneficiaryCreate(
            ...         name="John Doe",
            ...         phone="+237680000000",
            ...         email="john@example.com",
            ...         country="CM",
            ...         currency="XAF",
            ...         type="mobile_money"
            ...     )
            ... )
            >>> print(beneficiary.beneficiary.id)
        """
        ...

    @abc.abstractmethod
    def retrieve(self, beneficiary_id: str) -> MaybeAwaitable[NotchPayBeneficiaryResponse]:
        """Retrieve beneficiary details.

        Args:
            beneficiary_id: Unique beneficiary identifier.

        Returns:
            NotchPayBeneficiaryResponse: Beneficiary response.

        Raises:
            NotchPayError: If beneficiary doesn't exist or in case of API error.

        References:
            https://developer.notchpay.co/api-reference/retrieve-a-beneficiary

        Example:
            >>> from notchpay import NotchPay
            >>> notchpay = NotchPay("your_api_key")
            >>> beneficiary = notchpay.beneficiaries.retrieve("ben_123456789")
            >>> print(beneficiary.beneficiary.name)
        """
        ...

    @abc.abstractmethod
    def update(self, beneficiary_id: str, data: NotchPayBeneficiaryUpdate) -> MaybeAwaitable[
        NotchPayBeneficiaryResponse]:
        """Update a beneficiary.

        Args:
            beneficiary_id: Unique beneficiary identifier.
            data: Beneficiary update data.

        Returns:
            NotchPayBeneficiaryResponse: Update response.

        Raises:
            NotchPayError: If beneficiary doesn't exist or in case of API error.

        References:
            https://developer.notchpay.co/api-reference/update-a-beneficiary

        Example:
            >>> from notchpay import NotchPay
            >>> notchpay = NotchPay("your_api_key")
            >>> beneficiary = notchpay.beneficiaries.update(
            ...     "ben_123456789",
            ...     NotchPayBeneficiaryUpdate(name="Jane Doe")
            ... )
            >>> print(beneficiary.beneficiary.name)
        """
        ...

    @abc.abstractmethod
    def delete(self, beneficiary_id: str) -> MaybeAwaitable[NotchPayResponse]:
        """Delete a beneficiary.

        Args:
            beneficiary_id: Unique identifier of the beneficiary to delete.

        Returns:
            NotchPayResponse: Deletion response.

        Raises:
            NotchPayError: If beneficiary cannot be deleted.

        References:
            https://developer.notchpay.co/api-reference/delete-a-beneficiary

        Example:
            >>> from notchpay import NotchPay
            >>> notchpay = NotchPay("your_api_key")
            >>> response = notchpay.beneficiaries.delete("ben_123456789")
            >>> print(response.message)
        """
        ...

    def _build_list_params(
            self,
            limit: int = 30,
            page: int = 1,
            search: Optional[str] = None,
            country: Optional[str] = None,
            type: Optional[str] = None,
    ) -> dict[str, Any]:
        params: dict[str, Any] = {"limit": limit, "page": page}
        if search is not None:
            params["search"] = search
        if country is not None:
            params["country"] = country
        if type is not None:
            params["type"] = type

        return {"params": params}

    def _check_create_params(self, data: NotchPayBeneficiaryCreate) -> None:
        if not data.get("name"):
            raise NotchPayError("Name is required")
        if not data.get("country"):
            raise NotchPayError("Country is required")
        if not data.get("currency"):
            raise NotchPayError("Currency is required")
        if not data.get("channel"):
            raise NotchPayError("Type is required")

        beneficiary_type = data.get("type")
        if beneficiary_type == "mobile_money" and not data.get("phone"):
            raise NotchPayError(
                "Phone is required for mobile_money beneficiaries")
        if beneficiary_type == "bank_account":
            if not data.get("account_number"):
                raise NotchPayError(
                    "Account number is required for bank_account beneficiaries")
            if not data.get("bank_code"):
                raise NotchPayError(
                    "Bank code is required for bank_account beneficiaries")
        if beneficiary_type == "cash_pickup" and not data.get("phone"):
            raise NotchPayError(
                "Phone is required for cash_pickup beneficiaries")
        if data.get('phone') and not data.get('account_number'):
            data['account_number'] = data['phone']


class NotchPayBeneficiaries(_NotchPayBeneficiaries, NotchPayBaseResource):
    """Synchronous client for Notchpay beneficiaries management.

    This class allows performing all beneficiary-related operations
    synchronously.

    Example:
        >>> from notchpay import NotchPay
        >>> notchpay = NotchPay(api_key="your_api_key")
        >>> beneficiaries = notchpay.beneficiaries.list(limit=5)
        >>> print(f"Number of beneficiaries: {len(beneficiaries.items)}")
    """

    def list(
            self,
            limit: int = 30,
            page: int = 1,
            search: Optional[str] = None,
            country: Optional[str] = None,
            type: Optional[str] = None,
    ) -> NotchPayResponseList[NotchPayBeneficiary]:
        params = super()._build_list_params(limit, page, search, country, type)
        return self._request(
            "GET",
            "/beneficiaries",
            response_model=NotchPayResponseList[NotchPayBeneficiary],
            **params
        )

    def create(self, data: NotchPayBeneficiaryCreate) -> NotchPayBeneficiaryResponse:
        self._check_create_params(data)
        return self._request(
            "POST",
            "/beneficiaries",
            response_model=NotchPayBeneficiaryResponse,
            json=data
        )

    def retrieve(self, beneficiary_id: str) -> NotchPayBeneficiaryResponse:
        return self._request(
            "GET",
            f"/beneficiaries/{beneficiary_id}",
            response_model=NotchPayBeneficiaryResponse,
        )

    def update(self, beneficiary_id: str, data: NotchPayBeneficiaryUpdate) -> NotchPayBeneficiaryResponse:
        return self._request(
            "PUT",
            f"/beneficiaries/{beneficiary_id}",
            response_model=NotchPayBeneficiaryResponse,
            json=data
        )

    def delete(self, beneficiary_id: str) -> NotchPayResponse:
        return self._request(
            "DELETE",
            f"/beneficiaries/{beneficiary_id}",
            response_model=NotchPayResponse,
        )


class AsyncNotchPayBeneficiaries(_NotchPayBeneficiaries, AsyncNotchPayBaseResource):
    """Asynchronous client for Notchpay beneficiaries management.

    This class allows performing all beneficiary-related operations
    asynchronously.

    Example:
        >>> import asyncio
        >>> from notchpay import AsyncNotchPay
        >>> async def main():
        ...    notchpay = AsyncNotchPay("your_api_key")
        ...    beneficiaries = await notchpay.beneficiaries.list(limit=5)
        ...    print(f"Number of beneficiaries: {len(beneficiaries.items)}")
        >>> asyncio.run(main())
    """

    @override
    async def list(
            self,
            limit: int = 30,
            page: int = 1,
            search: Optional[str] = None,
            country: Optional[str] = None,
            type: Optional[str] = None,
    ) -> NotchPayResponseList[NotchPayBeneficiary]:
        params = super()._build_list_params(limit, page, search, country, type)
        return await self._request(
            "GET",
            "/beneficiaries",
            response_model=NotchPayResponseList[NotchPayBeneficiary],
            **params
        )

    async def create(self, data: NotchPayBeneficiaryCreate) -> NotchPayBeneficiaryResponse:
        self._check_create_params(data)
        return await self._request(
            "POST",
            "/beneficiaries",
            response_model=NotchPayBeneficiaryResponse,
            json=data
        )

    async def retrieve(self, beneficiary_id: str) -> NotchPayBeneficiaryResponse:
        return await self._request(
            "GET",
            f"/beneficiaries/{beneficiary_id}",
            response_model=NotchPayBeneficiaryResponse,
        )

    async def update(self, beneficiary_id: str, data: NotchPayBeneficiaryUpdate) -> NotchPayBeneficiaryResponse:
        return await self._request(
            "PUT",
            f"/beneficiaries/{beneficiary_id}",
            response_model=NotchPayBeneficiaryResponse,
            json=data
        )

    async def delete(self, beneficiary_id: str) -> NotchPayResponse:
        return await self._request(
            "DELETE",
            f"/beneficiaries/{beneficiary_id}",
            response_model=NotchPayResponse,
        )
