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

from typing_extensions import override

from ._base import NotchPayBaseResource, AsyncNotchPayBaseResource
from .exceptions import NotchPayError
from .schemas import NotchPayResponseList, NotchPayResponse, NotchPayChannels
from .schemas.base import MaybeAwaitable
from .schemas.transfers import (
    NotchPayTransfer,
    NotchPayTransferCreate,
    NotchPayTransferResponse,
    NotchPayBulkTransferCreate,
    NotchPayBulkTransferResponse,
)

__all__ = [
    "NotchPayTransfers",
    "AsyncNotchPayTransfers",
]


class _NotchPayTransfers(abc.ABC):
    """Abstract base class for Notchpay transfers management."""

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

        Args:
            limit: Number of items per page (default: 30, max: 100).
            page: Page number (default: 1).
            search: Search by reference or beneficiary.
            status: Filter by status.
            channels: Filter by transfer channels.
            date_start: Start date or str in YYYY-MM-DD format.
            date_end: End date or str in YYYY-MM-DD format.

        Returns:
            NotchPayResponseList[NotchPayTransfer]: Paginated list of transfers.

        Raises:
            NotchPayError: In case of API request error.

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

        Example:
            >>> from notchpay import NotchPay
            >>> notchpay = NotchPay("your_api_key", grant_key="your_private_key")
            >>> transfers = notchpay.transfers.list(limit=10, status="complete")
            >>> for transfer in transfers.items:
            ...     print(transfer.id, transfer.amount)
        """
        ...

    @abc.abstractmethod
    def create(self, data: NotchPayTransferCreate) -> MaybeAwaitable[NotchPayTransferResponse]:
        """Create a new transfer.

        Args:
            data: Transfer creation data.

        Returns:
            NotchPayTransferResponse: Transfer creation response.

        Raises:
            NotchPayError: In case of creation error.

        References:
            https://developer.notchpay.co/api-reference/initiate-a-transfer

        Example:
            >>> from notchpay import NotchPay
            >>> notchpay = NotchPay("your_api_key", grant_key="your_private_key")
            >>> transfer = notchpay.transfers.create(
            ...     NotchPayTransferCreate(
            ...         amount=5000,
            ...         currency="XAF",
            ...         beneficiary="ben_123456789",
            ...         channel=NotchPayChannels.CM_MTN,
            ...         description="Salary payment"
            ...     )
            ... )
            >>> print(transfer.transfer.id)
        """
        ...

    @abc.abstractmethod
    def retrieve(self, transfer_id: str) -> MaybeAwaitable[NotchPayTransferResponse]:
        """Retrieve transfer details.

        Args:
            transfer_id: Unique transfer identifier or reference.

        Returns:
            NotchPayTransferResponse: Transfer response.

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

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

        Example:
            >>> from notchpay import NotchPay
            >>> notchpay = NotchPay("your_api_key", grant_key="your_private_key")
            >>> transfer = notchpay.transfers.retrieve("trf_123456789")
            >>> print(transfer.transfer.status)
        """
        ...

    @abc.abstractmethod
    def cancel(self, transfer_id: str) -> MaybeAwaitable[NotchPayResponse]:
        """Cancel a pending transfer.

        Args:
            transfer_id: Unique identifier or reference of the transfer to cancel.

        Returns:
            NotchPayResponse: Cancellation response.

        Raises:
            NotchPayError: If transfer cannot be cancelled.

        References:
            https://developer.notchpay.co/api-reference/cancel-a-transfer

        Example:
            >>> from notchpay import NotchPay
            >>> notchpay = NotchPay("your_api_key", grant_key="your_private_key")
            >>> response = notchpay.transfers.cancel("trf_123456789")
            >>> print(response.message)
        """
        ...

    @abc.abstractmethod
    def create_bulk(self, data: NotchPayBulkTransferCreate) -> MaybeAwaitable[NotchPayBulkTransferResponse]:
        """Create a bulk transfer.

        Args:
            data: Bulk transfer creation data.

        Returns:
            NotchPayBulkTransferResponse: Bulk transfer creation response.

        Raises:
            NotchPayError: In case of creation error.

        Example:
            >>> from notchpay.schemas import NotchPayBulkTransferItem
            >>> from notchpay import NotchPay
            >>> notchpay = NotchPay("your_api_key", grant_key="your_private_key")
            >>> bulk = notchpay.transfers.create_bulk(
            ...     NotchPayBulkTransferCreate(
            ...         currency="XAF",
            ...         description="January 2023 Payroll",
            ...         transfers=[
            ...             NotchPayBulkTransferItem(
            ...                amount=5000,
            ...                beneficiary="ben_123",
            ...                channel=NotchPayChannels.CM_MTN
            ...             ),
            ...             NotchPayBulkTransferItem(
            ...                amount=7000,
            ...                beneficiary="ben_456",
            ...                channel=NotchPayChannels.CM_ORANGE
            ...             ),
            ...         ]
            ...     )
            ... )
            >>> print(bulk.bulk_transfer.id)
        """
        ...

    @abc.abstractmethod
    def retrieve_bulk(self, bulk_transfer_id: str) -> MaybeAwaitable[NotchPayBulkTransferResponse]:
        """Retrieve bulk transfer details.

        Args:
            bulk_transfer_id: Unique bulk transfer identifier.

        Returns:
            NotchPayBulkTransferResponse: Bulk transfer response.

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

        Example:
            >>> from notchpay import NotchPay
            >>> notchpay = NotchPay("your_api_key", grant_key="your_private_key")
            >>> bulk = notchpay.transfers.retrieve_bulk("btrf_123456789")
            >>> print(f"Total: {bulk.bulk_transfer.total_amount}")
        """
        ...

    def _build_list_params(
            self,
            limit: int = 30,
            page: int = 1,
            search: Optional[str] = None,
            status: Optional[str] = None,
            channels: Optional[List[NotchPayChannels]] = 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 status is not None:
            params["status"] = status
        if channels is not None:
            params["channels"] = ",".join(channels)
        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: NotchPayTransferCreate) -> None:
        if not data.get("beneficiary") and not data.get("beneficiary_data"):
            raise NotchPayError("Either beneficiary or beneficiary_data is required")
        if data.get("beneficiary") and data.get("beneficiary_data"):
            raise NotchPayError("Cannot provide both beneficiary and beneficiary_data")

    def _check_bulk_create_params(self, data: NotchPayBulkTransferCreate) -> None:
        if not data.get("transfers") or len(data.get("transfers", [])) == 0:
            raise NotchPayError("At least one transfer is required")


class NotchPayTransfers(_NotchPayTransfers, NotchPayBaseResource):
    """Synchronous client for Notchpay transfers management.

    This class allows performing all transfer-related operations
    synchronously.

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

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

    def create(self, data: NotchPayTransferCreate) -> NotchPayTransferResponse:
        self._check_create_params(data)
        return self._request(
            "POST",
            "/transfers",
            response_model=NotchPayTransferResponse,
            json=data
        )

    def retrieve(self, transfer_id: str) -> NotchPayTransferResponse:
        return self._request(
            "GET",
            f"/transfers/{transfer_id}",
            response_model=NotchPayTransferResponse,
        )

    def cancel(self, transfer_id: str) -> NotchPayResponse:
        return self._request(
            "DELETE",
            f"/transfers/{transfer_id}",
            response_model=NotchPayResponse,
        )

    def create_bulk(self, data: NotchPayBulkTransferCreate) -> NotchPayBulkTransferResponse:
        self._check_bulk_create_params(data)
        return self._request(
            "POST",
            "/transfers/bulk",
            response_model=NotchPayBulkTransferResponse,
            json=data
        )

    def retrieve_bulk(self, bulk_transfer_id: str) -> NotchPayBulkTransferResponse:
        return self._request(
            "GET",
            f"/transfers/bulk/{bulk_transfer_id}",
            response_model=NotchPayBulkTransferResponse,
        )


class AsyncNotchPayTransfers(_NotchPayTransfers, AsyncNotchPayBaseResource):
    """Asynchronous client for Notchpay transfers management.

    This class allows performing all transfer-related operations
    asynchronously.

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

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

    async def create(self, data: NotchPayTransferCreate) -> NotchPayTransferResponse:
        self._check_create_params(data)
        return await self._request(
            "POST",
            "/transfers",
            response_model=NotchPayTransferResponse,
            json=data
        )

    async def retrieve(self, transfer_id: str) -> NotchPayTransferResponse:
        return await self._request(
            "GET",
            f"/transfers/{transfer_id}",
            response_model=NotchPayTransferResponse,
        )

    async def cancel(self, transfer_id: str) -> NotchPayResponse:
        return await self._request(
            "DELETE",
            f"/transfers/{transfer_id}",
            response_model=NotchPayResponse,
        )

    async def create_bulk(self, data: NotchPayBulkTransferCreate) -> NotchPayBulkTransferResponse:
        self._check_bulk_create_params(data)
        return await self._request(
            "POST",
            "/transfers/bulk",
            response_model=NotchPayBulkTransferResponse,
            json=data
        )

    async def retrieve_bulk(self, bulk_transfer_id: str) -> NotchPayBulkTransferResponse:
        return await self._request(
            "GET",
            f"/transfers/bulk/{bulk_transfer_id}",
            response_model=NotchPayBulkTransferResponse,
        )
