"""Heimdall authentication client for the Hera2 SDK.

Mirrors the Java HeimdallClient from the backend, providing token
validation against the Heimdall authorize API before forwarding
requests to the OpenMetadata server.
"""
from __future__ import annotations

import json
import logging
import ssl
from dataclasses import dataclass, field
from datetime import datetime
from typing import List, Optional

import requests
from requests.adapters import HTTPAdapter

logger = logging.getLogger("metadata.sdk.heimdall")


@dataclass
class HeimdallConfig:
    """Configuration for the Heimdall authorization service.

    Mirrors hera/config/config.yaml authenticationConfiguration.heimdallConfiguration:
    enabled, baseUrl, timeout, trustAll, fallbackOnBasic.
    """

    base_url: str
    enabled: bool = True
    timeout: int = 10
    trust_all: bool = True
    fallback_on_basic: bool = True
    role_tag_prefixes: List[str] = field(default_factory=lambda: ["roles:id:"])
    team_tag_prefixes: List[str] = field(
        default_factory=lambda: ["teams:id:", "users:id:"]
    )

    @classmethod
    def from_config_dict(cls, data: dict) -> "HeimdallConfig":
        """Build from a dict with YAML/JSON keys (e.g. baseUrl, trustAll, fallbackOnBasic)."""
        base_url = data.get("baseUrl") or data.get("base_url")
        if not base_url:
            raise ValueError("heimdallConfiguration.baseUrl is required")
        return cls(
            base_url=base_url,
            enabled=data.get("enabled", True),
            timeout=int(data.get("timeout", 10)),
            trust_all=data.get("trustAll", data.get("trust_all", True)),
            fallback_on_basic=data.get("fallbackOnBasic", data.get("fallback_on_basic", True)),
            role_tag_prefixes=data.get("roleTagPrefixes", data.get("role_tag_prefixes", ["roles:id:"])),
            team_tag_prefixes=data.get("teamTagPrefixes", data.get("team_tag_prefixes", ["teams:id:", "users:id:"])),
        )


HeimdallConfiguration = HeimdallConfig


@dataclass
class HeimdallAuthorizeResult:
    id: Optional[str] = None
    tags: Optional[List[str]] = None


@dataclass
class HeimdallAuthorizeResponse:
    allow: Optional[bool] = None
    valid: Optional[bool] = None
    result: Optional[HeimdallAuthorizeResult] = None
    error: Optional[dict] = None

    @classmethod
    def from_dict(cls, data: dict) -> "HeimdallAuthorizeResponse":
        result_data = data.get("result")
        result = (
            HeimdallAuthorizeResult(
                id=result_data.get("id"), tags=result_data.get("tags")
            )
            if result_data
            else None
        )
        return cls(
            allow=data.get("allow"),
            valid=data.get("valid"),
            result=result,
            error=data.get("error"),
        )


@dataclass
class HeimdallUserProfile:
    id: Optional[str] = None
    email: Optional[str] = None
    name: Optional[str] = None
    type: Optional[str] = None
    tags: Optional[List[str]] = None

    @classmethod
    def from_dict(cls, data: dict) -> "HeimdallUserProfile":
        return cls(
            id=data.get("id"),
            email=data.get("email"),
            name=data.get("name"),
            type=data.get("type"),
            tags=data.get("tags"),
        )


class HeimdallAuthError(Exception):
    """Raised when Heimdall rejects a token or is unreachable."""


class _TrustAllAdapter(HTTPAdapter):
    """HTTP adapter that skips SSL verification."""

    def init_poolmanager(self, *args, **kwargs):
        ctx = ssl.create_default_context()
        ctx.check_hostname = False
        ctx.verify_mode = ssl.CERT_NONE
        kwargs["ssl_context"] = ctx
        return super().init_poolmanager(*args, **kwargs)


class HeimdallClient:
    """Python client for the Heimdall authorization service.

    Replicates the contract of the Java ``HeimdallClient``:
    - POST ``/api/v1/authorize``  with ``{"token": "..."}``
    - GET  ``/api/v1/users/{id}`` with ``Authorization: Bearer ...``
    """

    def __init__(self, config: HeimdallConfig):
        self._config = config
        self._base_url = config.base_url.rstrip("/")
        self._timeout = config.timeout
        self._session = requests.Session()
        if config.trust_all:
            self._session.verify = False
            self._session.mount("https://", _TrustAllAdapter())

    def authorize(self, bearer_token: str) -> HeimdallAuthorizeResponse:
        """Validate a token against Heimdall's authorize endpoint.

        Args:
            bearer_token: Raw token string (with or without ``Bearer `` prefix).

        Returns:
            Parsed authorize response.

        Raises:
            HeimdallAuthError: When the request fails or Heimdall rejects the token.
        """
        token = self._extract_token(bearer_token)
        url = f"{self._base_url}/api/v1/authorize"

        try:
            resp = self._session.post(
                url,
                json={"token": token},
                headers={"Content-Type": "application/json"},
                timeout=self._timeout,
            )
        except requests.RequestException as exc:
            raise HeimdallAuthError(
                f"Failed to reach Heimdall at {url}: {exc}"
            ) from exc

        try:
            data = resp.json()
        except (json.JSONDecodeError, ValueError) as exc:
            raise HeimdallAuthError(
                f"Heimdall returned non-JSON response (status {resp.status_code})"
            ) from exc

        parsed = HeimdallAuthorizeResponse.from_dict(data)

        if resp.status_code >= 400 or not parsed.allow:
            error_msg = "Access denied"
            if parsed.error and isinstance(parsed.error, dict):
                error_msg = parsed.error.get("message", error_msg)
            raise HeimdallAuthError(
                f"Heimdall authorization failed: {error_msg} "
                f"(status={resp.status_code})"
            )

        return parsed

    def get_user(
        self, user_id: str, bearer_token: str
    ) -> HeimdallUserProfile:
        """Fetch a user profile from Heimdall.

        Args:
            user_id: The Heimdall user identifier.
            bearer_token: Bearer token for authorization.

        Returns:
            Parsed user profile.

        Raises:
            HeimdallAuthError: When the request fails.
        """
        url = f"{self._base_url}/api/v1/users/{user_id}"
        token = self._extract_token(bearer_token)

        try:
            resp = self._session.get(
                url,
                headers={"Authorization": f"Bearer {token}"},
                timeout=self._timeout,
            )
        except requests.RequestException as exc:
            raise HeimdallAuthError(
                f"Failed to reach Heimdall at {url}: {exc}"
            ) from exc

        if resp.status_code >= 400:
            raise HeimdallAuthError(
                f"Heimdall getUser failed (status {resp.status_code})"
            )

        return HeimdallUserProfile.from_dict(resp.json())

    @staticmethod
    def _extract_token(bearer_token: str) -> str:
        if bearer_token and bearer_token.lower().startswith("bearer "):
            return bearer_token[7:]
        return bearer_token

    def close(self):
        self._session.close()
