"""
HTTP session handler for Wave API requests.
Handles authentication, request signing, and error mapping.
"""

import hashlib
import hmac
import time
from typing import Any, Dict, Optional
from urllib.parse import urljoin

import requests

from .exceptions import (
    WaveAPIError,
    WaveAuthError,
    WaveIdempotencyError,
    WaveNotFoundError,
    WaveRateLimitError,
    WaveServerError,
    WaveValidationError,
)

BASE_URL = "https://api.wave.com"


class WaveSession:
    """
    Manages HTTP communication with the Wave API.

    Handles:
    - Bearer token authentication
    - Optional HMAC-SHA256 request signing
    - Automatic error mapping to typed exceptions
    - Configurable timeouts and retries
    """

    def __init__(
        self,
        api_key: str,
        signing_secret: Optional[str] = None,
        base_url: str = BASE_URL,
        timeout: int = 30,
    ):
        """
        Initialize the Wave HTTP session.

        Args:
            api_key: Your Wave Business API key.
            signing_secret: Optional signing secret for request signing (HMAC-SHA256).
            base_url: Override the default API base URL (useful for testing).
            timeout: Request timeout in seconds (default: 30).
        """
        self.api_key = api_key
        self.signing_secret = signing_secret
        self.base_url = base_url.rstrip("/")
        self.timeout = timeout
        self._session = requests.Session()
        self._session.headers.update(
            {
                "Authorization": f"Bearer {api_key}",
                "Content-Type": "application/json",
                "User-Agent": "wave-business-api-python/1.0.0",
            }
        )

    def _sign_request(self, body: str = "") -> str:
        """
        Generate a Wave-Signature header value.

        Args:
            body: Raw request body string. Use empty string for GET requests.

        Returns:
            Formatted Wave-Signature header value.
        """
        timestamp = str(int(time.time()))
        payload = timestamp + body
        signature = hmac.new(
            self.signing_secret.encode("utf-8"),
            payload.encode("utf-8"),
            hashlib.sha256,
        ).hexdigest()
        return f"t={timestamp},v1={signature}"

    def _build_url(self, path: str) -> str:
        return urljoin(self.base_url, path)

    def _handle_response(self, response: requests.Response) -> Any:
        """Parse the response and raise typed exceptions on errors."""
        # No-content responses
        if response.status_code in (200, 201, 204) and not response.content:
            return None

        # Try to parse JSON
        try:
            data = response.json()
        except ValueError:
            data = {}

        if response.status_code in (200, 201):
            return data

        # Map status codes to exceptions
        error_code = data.get("code") or data.get("error_code") or data.get("error", {}).get("code", "unknown")
        message = data.get("message") or data.get("error_message") or data.get("error", {}).get("message", str(data))
        details = data.get("details")

        if response.status_code in (401, 403):
            raise WaveAuthError(message, response.status_code, error_code, details)
        elif response.status_code == 404:
            raise WaveNotFoundError(message, response.status_code, error_code, details)
        elif response.status_code in (400, 422):
            raise WaveValidationError(message, response.status_code, error_code, details)
        elif response.status_code == 409:
            raise WaveIdempotencyError(message, response.status_code, error_code, details)
        elif response.status_code == 429:
            raise WaveRateLimitError(message, response.status_code, error_code, details)
        elif response.status_code >= 500:
            raise WaveServerError(message, response.status_code, error_code, details)
        else:
            raise WaveAPIError(message, response.status_code, error_code, details)

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

        Args:
            path: API endpoint path (e.g., '/v1/balance').
            params: Optional query parameters dict.

        Returns:
            Parsed JSON response.
        """
        url = self._build_url(path)
        headers = {}
        if self.signing_secret:
            headers["Wave-Signature"] = self._sign_request("")  # empty body for GET
        response = self._session.get(url, params=params, headers=headers, timeout=self.timeout)
        return self._handle_response(response)

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

        Args:
            path: API endpoint path.
            json: Optional request body dict.
            idempotency_key: Optional idempotency key for safe retries.

        Returns:
            Parsed JSON response.
        """
        import json as json_lib

        url = self._build_url(path)
        headers = {}
        body_str = json_lib.dumps(json, separators=(",", ":")) if json else ""

        if self.signing_secret:
            headers["Wave-Signature"] = self._sign_request(body_str)
        if idempotency_key:
            headers["Idempotency-Key"] = idempotency_key

        response = self._session.post(url, json=json, headers=headers, timeout=self.timeout)
        return self._handle_response(response)

    def put(self, path: str, json: Optional[Dict] = None) -> Any:
        """
        Perform a PUT request.

        Args:
            path: API endpoint path.
            json: Request body dict.

        Returns:
            Parsed JSON response.
        """
        import json as json_lib

        url = self._build_url(path)
        headers = {}
        body_str = json_lib.dumps(json, separators=(",", ":")) if json else ""
        if self.signing_secret:
            headers["Wave-Signature"] = self._sign_request(body_str)
        response = self._session.put(url, json=json, headers=headers, timeout=self.timeout)
        return self._handle_response(response)

    def delete(self, path: str) -> Any:
        """
        Perform a DELETE request.

        Args:
            path: API endpoint path.

        Returns:
            None on success (204 No Content).
        """
        url = self._build_url(path)
        headers = {}
        if self.signing_secret:
            headers["Wave-Signature"] = self._sign_request("")
        response = self._session.delete(url, headers=headers, timeout=self.timeout)
        return self._handle_response(response)
