from __future__ import annotations

from abc import ABC, abstractmethod
from typing import Any

import httpx

from src.connectors.sdk.context import ConnectorContext
from src.core.logging import get_logger

logger = get_logger("connector_auth")


class AuthHandler(ABC):
    @abstractmethod
    async def apply(self, request: httpx.Request, context: ConnectorContext) -> httpx.Request:
        ...

    @abstractmethod
    async def refresh(self, context: ConnectorContext) -> bool:
        return True


class BasicAuthHandler(AuthHandler):
    def __init__(self, username_key: str = "username", password_key: str = "password") -> None:
        self._username_key = username_key
        self._password_key = password_key

    async def apply(self, request: httpx.Request, context: ConnectorContext) -> httpx.Request:
        import base64

        username = context.get_secret(self._username_key) or context.get_config(self._username_key, "")
        password = context.get_secret(self._password_key) or context.get_config(self._password_key, "")
        credentials = base64.b64encode(f"{username}:{password}".encode()).decode()
        request.headers["Authorization"] = f"Basic {credentials}"
        return request

    async def refresh(self, context: ConnectorContext) -> bool:
        return True


class BearerAuthHandler(AuthHandler):
    def __init__(self, token_key: str = "access_token") -> None:
        self._token_key = token_key
        self._token: str | None = None

    async def apply(self, request: httpx.Request, context: ConnectorContext) -> httpx.Request:
        token = self._token or context.get_secret(self._token_key) or context.get_config(self._token_key, "")
        if token:
            request.headers["Authorization"] = f"Bearer {token}"
        return request

    async def refresh(self, context: ConnectorContext) -> bool:
        self._token = None
        return True


class APIKeyAuthHandler(AuthHandler):
    def __init__(self, header_name: str = "X-API-Key", key_secret: str = "api_key") -> None:
        self._header_name = header_name
        self._key_secret = key_secret

    async def apply(self, request: httpx.Request, context: ConnectorContext) -> httpx.Request:
        api_key = context.get_secret(self._key_secret) or context.get_config(self._key_secret, "")
        if api_key:
            request.headers[self._header_name] = api_key
        return request

    async def refresh(self, context: ConnectorContext) -> bool:
        return True


class OAuth2Handler(AuthHandler):
    def __init__(
        self,
        token_url_key: str = "token_url",
        client_id_key: str = "client_id",
        client_secret_key: str = "client_secret",
    ) -> None:
        self._token_url_key = token_url_key
        self._client_id_key = client_id_key
        self._client_secret_key = client_secret_key
        self._access_token: str | None = None
        self._token_expires: float = 0

    async def apply(self, request: httpx.Request, context: ConnectorContext) -> httpx.Request:
        import time

        if not self._access_token or time.time() >= self._token_expires:
            await self._fetch_token(context)

        if self._access_token:
            request.headers["Authorization"] = f"Bearer {self._access_token}"
        return request

    async def _fetch_token(self, context: ConnectorContext) -> None:
        import time

        token_url = context.require_config(self._token_url_key)
        client_id = context.require_secret(self._client_id_key)
        client_secret = context.require_secret(self._client_secret_key)

        async with httpx.AsyncClient() as client:
            resp = await client.post(
                token_url,
                data={
                    "grant_type": "client_credentials",
                    "client_id": client_id,
                    "client_secret": client_secret,
                },
                headers={"Content-Type": "application/x-www-form-urlencoded"},
            )
            resp.raise_for_status()
            data = resp.json()
            self._access_token = data["access_token"]
            self._token_expires = time.time() + data.get("expires_in", 3600) - 30

        logger.info("oauth2_token_acquired", extra={"token_url": token_url})

    async def refresh(self, context: ConnectorContext) -> bool:
        self._access_token = None
        self._token_expires = 0
        return True
