import abc
import hashlib
import hmac
from typing import Optional, Union

import httpx

__version__ = "0.1.0"

__all__ = [
    "NotchPay",
    "AsyncNotchPay",
]

from ._balance import NotchPayBalance, AsyncNotchPayBalance
from ._beneficiaries import NotchPayBeneficiaries, AsyncNotchPayBeneficiaries
from ._channels import NotchPayChannels, AsyncNotchPayChannels
from ._customers import NotchPayCustomers, AsyncNotchPayCustomers
from ._payments import NotchPayPayments, AsyncNotchPayPayments
from ._transfers import NotchPayTransfers, AsyncNotchPayTransfers
from ._utils import force_async, force_sync
from ._webhooks import NotchPayWebhooks, AsyncNotchPayWebhooks
from .exceptions import NotchPayError, NotchPayValidationError, NotchPayAuthenticationError, NotchPayAPIError


class _NotchPayBase(abc.ABC):

    def __init__(self, public_key: str, *, grant_key: Optional[str] = None, debug: Optional[bool] = False):
        self._public_key = public_key
        self._private_key: Optional[str] = grant_key
        self._debug = debug
        self._client: Union[httpx.Client, httpx.AsyncClient] = httpx.Client()
        self._base_url = "https://api.notchpay.co"
        self._headers = {
            "Content-Type": "application/json",
            "Accept": "application/json",
            "User-Agent": f"NotchPay-Python/{__version__}",
        }

    def set_grant_key(self, grant_key: str) -> None:
        self._private_key = grant_key

    def set_debug(self, debug: bool) -> None:
        self._debug = debug

    @staticmethod
    def verify_webhook_signature(payload: str, signature: str, secret: str) -> bool:
        """Verify a webhook signature.

        Args:
            payload: The raw webhook content (string).
            signature: The signature provided in the X-Notchpay-Signature header.
            secret: The webhook secret.

        Returns:
            bool: True if the signature is valid, False otherwise.

        Example:
            >>> from notchpay import NotchPay
            >>> payload = request.body.decode('utf-8')
            >>> signature = request.headers.get('X-Notchpay-Signature')
            >>> webhook_secret = 'your_webhook_secret'
            >>> is_valid = NotchPay.verify_webhook_signature(payload, signature, webhook_secret)
            >>> if not is_valid:
            ...     return 'Invalid signature', 400
        """
        calculated_signature = hmac.new(
            secret.encode('utf-8'),
            payload.encode('utf-8'),
            hashlib.sha256
        ).hexdigest()
        return hmac.compare_digest(calculated_signature, signature)

    def _add_request_hooks(self, request: httpx.Request) -> None:
        if self._public_key:
            request.headers["Authorization"] = f"{self._public_key}"
        if self._private_key:
            request.headers["X-Grant"] = f"{self._private_key}"

    def _add_response_hooks(self, response: httpx.Response) -> None:
        try:
            response.raise_for_status()
        except httpx.HTTPStatusError:
            if isinstance(self._client, httpx.AsyncClient):
                force_sync(response.aread)()
            else:
                response.read()
            data = response.json()
            if self._debug:
                print(data)
            if response.status_code == 401:
                raise NotchPayAuthenticationError(
                    code=data.get('code', None),
                    status=data.get('status', None),
                    message=data.get('message', None)
                )
            elif response.is_client_error:
                raise NotchPayValidationError(
                    code=data.get('code', None),
                    status=data.get('status', None),
                    message=data.get('message', None),
                    errors=data.get('errors', None)
                )
            else:
                raise NotchPayAPIError(
                    code=data.get('code', None),
                    status=data.get('status', None),
                    message=data.get('message', None)
                )


class NotchPay(_NotchPayBase):
    def __init__(self, public_key: str, *, grant_key: Optional[str] = None, debug: Optional[bool] = False):
        super().__init__(public_key, grant_key=grant_key, debug=debug)
        self._client = httpx.Client(
            base_url=self._base_url,
            headers=self._headers,
            event_hooks={
                "request": [
                    self._add_request_hooks
                ],
                "response": [
                    self._add_response_hooks
                ]
            }
        )

        self.channels = NotchPayChannels(self._client)
        self.customers = NotchPayCustomers(self._client)
        self.payments = NotchPayPayments(self._client)
        self.balance = NotchPayBalance(self._client)
        self.beneficiaries = NotchPayBeneficiaries(self._client)
        self.webhooks = NotchPayWebhooks(self._client)
        self.transfers = NotchPayTransfers(self._client)


class AsyncNotchPay(_NotchPayBase):
    def __init__(self, public_key: str, *, grant_key: Optional[str] = None, debug: Optional[bool] = False):
        super().__init__(public_key, grant_key=grant_key, debug=debug)
        self._client = httpx.AsyncClient(
            base_url=self._base_url,
            headers=self._headers,
            event_hooks={
                "request": [
                    force_async(self._add_request_hooks)
                ],
                "response": [
                    force_async(self._add_response_hooks)
                ]
            }
        )

        self.channels = AsyncNotchPayChannels(self._client)
        self.customers = AsyncNotchPayCustomers(self._client)
        self.payments = AsyncNotchPayPayments(self._client)
        self.balance = AsyncNotchPayBalance(self._client)
        self.beneficiaries = AsyncNotchPayBeneficiaries(self._client)
        self.webhooks = AsyncNotchPayWebhooks(self._client)
        self.transfers = AsyncNotchPayTransfers(self._client)
