import time
import json
import hmac
import hashlib
import base64
from typing import Any, Optional
import httpx
from .api import ContactAPI, TimesheetAPI


class EzeasClient:
    """
    Async client for ezeas Client API using HS256 JWT.

    Base URL rules:
      - environment="live"     → https://{tenant}.ezeas.com
      - environment="sandbox"  → http://localhost:8000
    """

    def __init__(
        self,
        client_id: str,
        client_secret: str,
        tenant: str,
        environment: str = "live",
    ):
        self.client_id = client_id
        self.client_secret = client_secret

        if environment == "sandbox":
            self.base_url = "http://localhost:8000"
        else:
            self.base_url = f"https://{tenant}.ezeas.com"

        self._cached_token: Optional[str] = None
        self._cached_expiry: int = 0
        self._client: Optional[httpx.AsyncClient] = None

        self._contact_api: Optional[ContactAPI] = None
        self._timesheet_api: Optional[TimesheetAPI] = None

    async def __aenter__(self) -> "EzeasClient":
        self._client = httpx.AsyncClient(base_url=self.base_url, timeout=30.0)
        return self

    async def __aexit__(self, exc_type, exc, tb) -> None:
        if self._client is not None:
            await self._client.aclose()
            self._client = None

    # -------------------------------------------------------------
    # Internal: JWT helpers
    # -------------------------------------------------------------
    def _b64(self, data: bytes) -> str:
        return base64.urlsafe_b64encode(data).rstrip(b"=").decode("ascii")

    def _sign_hs256(self, message: bytes) -> str:
        signature = hmac.new(
            key=self.client_secret.encode("utf-8"),
            msg=message,
            digestmod=hashlib.sha256,
        ).digest()
        return self._b64(signature)

    def _generate_token(self) -> str:
        now = int(time.time())
        exp = now + 900  # 15 minutes

        header = {"alg": "HS256", "typ": "JWT"}
        payload = {
            "sub": self.client_id,
            "client_id": self.client_id,
            "iat": now,
            "exp": exp,
        }

        header_b64 = self._b64(json.dumps(header).encode("utf-8"))
        payload_b64 = self._b64(json.dumps(payload).encode("utf-8"))
        message = f"{header_b64}.{payload_b64}".encode("utf-8")

        signature = self._sign_hs256(message)
        token = f"{header_b64}.{payload_b64}.{signature}"

        self._cached_token = token
        self._cached_expiry = exp
        return token

    def _get_token(self) -> str:
        now = int(time.time())
        if self._cached_token and now < self._cached_expiry - 60:
            return self._cached_token
        return self._generate_token()

    # -------------------------------------------------------------
    # Internal: HTTP request
    # -------------------------------------------------------------
    async def _request(
        self,
        method: str,
        path: str,
        json_body: Optional[Any] = None,
    ) -> Any:
        if self._client is None:
            self._client = httpx.AsyncClient(
                base_url=self.base_url,
                timeout=30.0,
            )

        token = self._get_token()
        headers = {
            "Authorization": f"Bearer {token}",
            "Content-Type": "application/json",
        }

        response = await self._client.request(
            method=method,
            url=path,
            headers=headers,
            json=json_body,
        )

        status = response.status_code

        if 200 <= status < 300:
            text = response.text.strip()
            return response.json() if text else None

        if 400 <= status < 500:
            try:
                detail = response.json()
            except Exception:
                detail = response.text
            raise Exception(f"{detail}")

        raise Exception(f"Internal server error {status}: {response.text}")

    @property
    def contact(self) -> ContactAPI:
        if self._contact_api is None:
            self._contact_api = ContactAPI(self)
        return self._contact_api

    @property
    def timesheet(self) -> TimesheetAPI:
        if self._timesheet_api is None:
            self._timesheet_api = TimesheetAPI(self)
        return self._timesheet_api
