import abc
from datetime import datetime
from typing import Any, Optional, Union

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.customers import NotchPayCustomer, NotchPayCustomerCreate, NotchPayCustomerUpdate, \
    NotchPayCustomerResponse, NotchPayPaymentMethodsResponse, NotchPayCustomerPaymentsResponse

__all__ = [
    "NotchPayCustomers",
    "AsyncNotchPayCustomers",
]


class _NotchPayCustomers(abc.ABC):
    """Abstract base class for Notchpay customers management."""

    @abc.abstractmethod
    def list(
            self,
            limit: int = 30,
            page: int = 1,
            search: Optional[str] = None,
            date_start: Optional[Union[datetime, str]] = None,
            date_end: Optional[Union[datetime, str]] = None,
    ) -> MaybeAwaitable[NotchPayResponseList[NotchPayCustomer]]:
        """List all customers.

        Args:
            limit: Number of items per page (default: 30).
            page: Page number (default: 1).
            search: Search by name, email or phone.
            date_start: Start date or `str` in YYYY-MM-DD format.
            date_end: End date or `str` in YYYY-MM-DD format.

        Returns:
            NotchPayResponseList[NotchPayCustomer]: Paginated list of customers.

        Raises:
            NotchPayError: In case of API request error.

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

        Example:
            >>> from notchpay import NotchPay
            >>> notchpay = NotchPay("your_api_key")
            >>> customers = notchpay.customers.list(limit=10)
            >>> for customer in customers.items:
            ...     print(customer.id, customer.email)
        """
        ...

    @abc.abstractmethod
    def create(self, data: NotchPayCustomerCreate) -> MaybeAwaitable[NotchPayCustomerResponse]:
        """Create a new customer.

        Args:
            data: Customer creation data.

        Returns:
            NotchPayCustomerResponse: Customer creation response.

        Raises:
            NotchPayError: In case of creation error.

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

        Example:
            >>> from notchpay import NotchPay
            >>> notchpay = NotchPay("your_api_key")
            >>> customer = notchpay.customers.create(
            ...     NotchPayCustomerCreate(
            ...         name="John Doe",
            ...         email="john@example.com",
            ...         phone="+237680000000"
            ...     )
            ... )
            >>> print(customer.customer.id)
        """
        ...

    @abc.abstractmethod
    def retrieve(self, customer_id: str) -> MaybeAwaitable[NotchPayCustomerResponse]:
        """Retrieve customer details.

        Args:
            customer_id: Unique customer identifier.

        Returns:
            NotchPayCustomerResponse: Customer response.

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

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

        Example:
            >>> from notchpay import NotchPay
            >>> notchpay = NotchPay("your_api_key")
            >>> customer = notchpay.customers.retrieve("cus.test_123456789")
            >>> print(customer.customer.email)
        """
        ...

    @abc.abstractmethod
    def update(self, customer_id: str, data: NotchPayCustomerUpdate) -> MaybeAwaitable[NotchPayCustomerResponse]:
        """Update a customer.

        Args:
            customer_id: Unique customer identifier.
            data: Customer update data.

        Returns:
            NotchPayCustomerResponse: Update response.

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

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

        Example:
            >>> from notchpay import NotchPay
            >>> notchpay = NotchPay("your_api_key")
            >>> customer = notchpay.customers.update(
            ...     "cus.test_123456789",
            ...     NotchPayCustomerUpdate(name="Jane Doe")
            ... )
            >>> print(customer.customer.name)
        """
        ...

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

        Args:
            customer_id: Unique identifier of the customer to delete.

        Returns:
            NotchPayResponse: Deletion response.

        Raises:
            NotchPayError: If customer cannot be deleted.

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

        Example:
            >>> from notchpay import NotchPay
            >>> notchpay = NotchPay("your_api_key")
            >>> response = notchpay.customers.delete("cus.test_123456789")
            >>> print(response.message)
        """
        ...

    @abc.abstractmethod
    def payments(self, customer_id: str, limit: int = 30, page: int = 1) -> MaybeAwaitable[
        NotchPayCustomerPaymentsResponse]:
        """List customer payments.

        Args:
            customer_id: Unique customer identifier.
            limit: Number of items per page (default: 30).
            page: Page number (default: 1).

        Returns:
            NotchPayCustomerPaymentsResponse: Paginated list of customer payments.

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

        References:
            https://developer.notchpay.co/api-reference/list-customers-payments

        Example:
            >>> from notchpay import NotchPay
            >>> notchpay = NotchPay("your_api_key")
            >>> payments = notchpay.customers.payments("cus.test_123456789", limit=10)
            >>> for payment in payments.items:
            ...     print(payment.reference, payment.amount)
        """
        ...

    @abc.abstractmethod
    def payment_methods(self, customer_id: str) -> MaybeAwaitable[NotchPayPaymentMethodsResponse]:
        """List customer payment methods.

        Args:
            customer_id: Unique customer identifier.

        Returns:
            NotchPayPaymentMethodsResponse: List of customer payment methods.

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

        References:
            https://developer.notchpay.co/api-reference/list-customers-payment-methods

        Example:
            >>> from notchpay import NotchPay
            >>> notchpay = NotchPay("your_api_key")
            >>> methods = notchpay.customers.payment_methods("cus.test_123456789")
            >>> for method in methods.items:
            ...     print(method.id, method.channel)
        """
        ...

    def _build_list_params(
            self,
            limit: int = 30,
            page: int = 1,
            search: Optional[str] = None,
            date_start: Optional[Union[datetime, str]] = None,
            date_end: Optional[Union[datetime, str]] = None,
    ) -> dict[str, Any]:
        params: dict[str, Any] = {"limit": limit, "page": page}
        if search is not None:
            params["search"] = search
        if date_start is not None:
            params["date_start"] = date_start.strftime("%Y-%m-%d") if isinstance(date_start, datetime) else date_start
        if date_end is not None:
            params["date_end"] = date_end.strftime("%Y-%m-%d") if isinstance(date_end, datetime) else date_end

        return {"params": params}

    def _check_create_params(self, data: NotchPayCustomerCreate) -> None:
        if data.get("email") is None and data.get("phone") is None:
            raise NotchPayError("Email or phone is required")


class NotchPayCustomers(_NotchPayCustomers, NotchPayBaseResource):
    """Synchronous client for Notchpay customers management.

    This class allows performing all customer-related operations
    synchronously.

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

    def list(
            self,
            limit: int = 30,
            page: int = 1,
            search: Optional[str] = None,
            date_start: Optional[Union[str, datetime]] = None,
            date_end: Optional[Union[str, datetime]] = None,
    ) -> NotchPayResponseList[NotchPayCustomer]:
        params = super()._build_list_params(limit, page, search, date_start, date_end)
        return self._request(
            "GET",
            "/customers",
            response_model=NotchPayResponseList[NotchPayCustomer],
            **params
        )

    def create(self, data: NotchPayCustomerCreate) -> NotchPayCustomerResponse:
        self._check_create_params(data)
        return self._request(
            "POST",
            "/customers",
            response_model=NotchPayCustomerResponse,
            json=data
        )

    def retrieve(self, customer_id: str) -> NotchPayCustomerResponse:
        return self._request(
            "GET",
            f"/customers/{customer_id}",
            response_model=NotchPayCustomerResponse,
        )

    def update(self, customer_id: str, data: NotchPayCustomerUpdate) -> NotchPayCustomerResponse:
        return self._request(
            "PUT",
            f"/customers/{customer_id}",
            response_model=NotchPayCustomerResponse,
            json=data
        )

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

    def payments(self, customer_id: str, limit: int = 30, page: int = 1) -> NotchPayCustomerPaymentsResponse:
        return self._request(
            "GET",
            f"/customers/{customer_id}/payments",
            response_model=NotchPayCustomerPaymentsResponse,
            params={"limit": limit, "page": page}
        )

    def payment_methods(self, customer_id: str) -> NotchPayPaymentMethodsResponse:
        return self._request(
            "GET",
            f"/customers/{customer_id}/payment_methods",
            response_model=NotchPayPaymentMethodsResponse,
        )


class AsyncNotchPayCustomers(_NotchPayCustomers, AsyncNotchPayBaseResource):
    """Asynchronous client for Notchpay customers management.

    This class allows performing all customer-related operations
    asynchronously.

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

    @override
    async def list(
            self,
            limit: int = 30,
            page: int = 1,
            search: Optional[str] = None,
            date_start: Optional[Union[str, datetime]] = None,
            date_end: Optional[Union[str, datetime]] = None,
    ) -> NotchPayResponseList[NotchPayCustomer]:
        params = super()._build_list_params(limit, page, search, date_start, date_end)
        return await self._request(
            "GET",
            "/customers",
            response_model=NotchPayResponseList[NotchPayCustomer],
            **params
        )

    async def create(self, data: NotchPayCustomerCreate) -> NotchPayCustomerResponse:
        self._check_create_params(data)
        return await self._request(
            "POST",
            "/customers",
            response_model=NotchPayCustomerResponse,
            json=data
        )

    async def retrieve(self, customer_id: str) -> NotchPayCustomerResponse:
        return await self._request(
            "GET",
            f"/customers/{customer_id}",
            response_model=NotchPayCustomerResponse,
        )

    async def update(self, customer_id: str, data: NotchPayCustomerUpdate) -> NotchPayCustomerResponse:
        return await self._request(
            "PUT",
            f"/customers/{customer_id}",
            response_model=NotchPayCustomerResponse,
            json=data
        )

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

    async def payments(self, customer_id: str, limit: int = 30, page: int = 1) -> NotchPayCustomerPaymentsResponse:
        return await self._request(
            "GET",
            f"/customers/{customer_id}/payments",
            response_model=NotchPayCustomerPaymentsResponse,
            params={"limit": limit, "page": page}
        )

    async def payment_methods(self, customer_id: str) -> NotchPayPaymentMethodsResponse:
        return await self._request(
            "GET",
            f"/customers/{customer_id}/payment_methods",
            response_model=NotchPayPaymentMethodsResponse,
        )
