import time
from typing import Any, Dict, Optional

import requests

from zyndpay.errors import (
    AuthenticationError,
    NotFoundError,
    RateLimitError,
    ValidationError,
    ZyndPayError,
)

DEFAULT_BASE_URL = "https://api.zyndpay.com/v1"
DEFAULT_TIMEOUT = 30
DEFAULT_MAX_RETRIES = 2
RETRYABLE_STATUS_CODES = {408, 429, 500, 502, 503, 504}


class HttpClient:
    def __init__(
        self,
        api_key: str,
        base_url: str = None,
        timeout: int = None,
        max_retries: int = None,
    ):
        if not api_key:
            raise AuthenticationError("API key is required")

        self.api_key = api_key
        self.base_url = (base_url or DEFAULT_BASE_URL).rstrip("/")
        self.timeout = timeout or DEFAULT_TIMEOUT
        self.max_retries = max_retries if max_retries is not None else DEFAULT_MAX_RETRIES
        self.session = requests.Session()
        self.session.headers.update(
            {
                "X-Api-Key": self.api_key,
                "Content-Type": "application/json",
                "User-Agent": "zyndpay-python/1.0.0",
            }
        )

    def request(
        self,
        method: str,
        path: str,
        json: Any = None,
        params: Dict[str, Any] = None,
        headers: Dict[str, str] = None,
        idempotency_key: str = None,
    ) -> Dict[str, Any]:
        url = f"{self.base_url}{path}"

        # Filter out None params
        if params:
            params = {k: v for k, v in params.items() if v is not None}

        extra_headers = dict(headers or {})
        if idempotency_key:
            extra_headers["Idempotency-Key"] = idempotency_key

        last_error = None

        for attempt in range(self.max_retries + 1):
            try:
                response = self.session.request(
                    method,
                    url,
                    json=json,
                    params=params,
                    headers=extra_headers,
                    timeout=self.timeout,
                )

                if response.ok:
                    return response.json()

                request_id = response.headers.get("x-request-id")

                if response.status_code not in RETRYABLE_STATUS_CODES:
                    body = response.json() if response.text else {}
                    raise self._create_error(response.status_code, body, request_id)

                if response.status_code == 429:
                    retry_after = int(response.headers.get("Retry-After", "1"))
                    if attempt < self.max_retries:
                        time.sleep(retry_after)
                        continue
                    raise RateLimitError("Rate limit exceeded", retry_after, request_id)

                last_error = ZyndPayError(
                    f"Request failed with status {response.status_code}",
                    response.status_code,
                    request_id,
                )

                if attempt < self.max_retries:
                    time.sleep(2**attempt * 0.5)

            except ZyndPayError:
                raise
            except Exception as e:
                last_error = e
                if attempt < self.max_retries:
                    time.sleep(2**attempt * 0.5)

        raise last_error or ZyndPayError("Request failed after retries")

    def get(self, path: str, params: Dict[str, Any] = None) -> Dict[str, Any]:
        return self.request("GET", path, params=params)

    def post(
        self,
        path: str,
        json: Any = None,
        idempotency_key: str = None,
        params: Dict[str, Any] = None,
    ) -> Dict[str, Any]:
        return self.request("POST", path, json=json, idempotency_key=idempotency_key, params=params)

    def delete(self, path: str) -> Dict[str, Any]:
        return self.request("DELETE", path)

    @staticmethod
    def _create_error(
        status_code: int, body: dict, request_id: Optional[str]
    ) -> ZyndPayError:
        message = body.get("message") or body.get("error") or f"Request failed with status {status_code}"

        if status_code == 401:
            return AuthenticationError(message, request_id)
        elif status_code == 400:
            return ValidationError(message, request_id)
        elif status_code == 404:
            return NotFoundError(message, request_id)
        elif status_code == 429:
            return RateLimitError(message, request_id=request_id)
        else:
            return ZyndPayError(message, status_code, request_id)
