"""Langfuse client configuration and lifecycle management."""

from __future__ import annotations

import os
from collections.abc import Mapping
from typing import Any, Callable

from langfuse import Langfuse
from pydantic import BaseModel, ConfigDict, model_validator

from dr_langfuse.errors.codes import ErrorCode
from dr_langfuse.errors.error_envelope import ErrorEnvelope

__all__ = [
    "ClientResolution",
    "LANGFUSE_BASE_URL_ENV",
    "LANGFUSE_HOST_ENV",
    "LANGFUSE_PUBLIC_KEY_ENV",
    "LANGFUSE_SECRET_KEY_ENV",
    "LangfuseConfig",
    "default_langfuse_client_factory",
    "resolve_client",
]

LANGFUSE_PUBLIC_KEY_ENV = "LANGFUSE_PUBLIC_KEY"
LANGFUSE_SECRET_KEY_ENV = "LANGFUSE_SECRET_KEY"
LANGFUSE_BASE_URL_ENV = "LANGFUSE_BASE_URL"
LANGFUSE_HOST_ENV = "LANGFUSE_HOST"


class LangfuseConfig(BaseModel):
    """Minimal Langfuse connection config."""

    public_key: str | None = None
    secret_key: str | None = None
    host: str | None = None

    @classmethod
    def from_env(cls, env: Mapping[str, str] | None = None) -> LangfuseConfig:
        environ = os.environ if env is None else env
        return cls(
            public_key=environ.get(LANGFUSE_PUBLIC_KEY_ENV),
            secret_key=environ.get(LANGFUSE_SECRET_KEY_ENV),
            host=environ.get(LANGFUSE_BASE_URL_ENV) or environ.get(LANGFUSE_HOST_ENV),
        )


class ClientResolution(BaseModel):
    """Result of resolving a Langfuse client. Exactly one of client or init_error must be set."""

    model_config = ConfigDict(frozen=True, arbitrary_types_allowed=True)

    client: Any | None = None
    init_error: ErrorEnvelope | None = None

    @model_validator(mode="after")
    def _check_mutual_exclusivity(self) -> ClientResolution:
        if (self.client is None) == (self.init_error is None):
            raise ValueError("Exactly one of client or init_error must be set")
        return self


class _CredentialsMissingError(ValueError):
    """Raised when required Langfuse credentials are unavailable."""


def default_langfuse_client_factory(config: LangfuseConfig) -> Langfuse:
    if not config.public_key or not config.secret_key:
        raise _CredentialsMissingError(
            "LANGFUSE_PUBLIC_KEY and LANGFUSE_SECRET_KEY are required"
        )

    return Langfuse(
        public_key=config.public_key,
        secret_key=config.secret_key,
        host=config.host,
    )


def resolve_client(
    *,
    client: Any | None,
    config: LangfuseConfig,
    client_factory: Callable[[LangfuseConfig], Any],
) -> ClientResolution:
    if client is not None:
        return ClientResolution(client=client)

    try:
        resolved = client_factory(config)
    except _CredentialsMissingError:
        return ClientResolution(
            init_error=ErrorEnvelope(
                code=ErrorCode.AUTH,
                message="Langfuse credentials are missing or invalid",
                retriable=False,
                details={"operation": "langfuse_client_init"},
            )
        )
    except ValueError:
        return ClientResolution(
            init_error=ErrorEnvelope(
                code=ErrorCode.MALFORMED_REQUEST,
                message="Langfuse client configuration is invalid",
                retriable=False,
                details={"operation": "langfuse_client_init"},
            )
        )
    except Exception as exc:
        return ClientResolution(
            init_error=ErrorEnvelope(exception=exc, operation="langfuse_client_init")
        )

    if resolved is None:
        return ClientResolution(
            init_error=ErrorEnvelope(
                code=ErrorCode.INTERNAL_ERROR,
                message="Langfuse client factory returned None",
                retriable=False,
                details={"operation": "langfuse_client_init"},
            )
        )
    return ClientResolution(client=resolved)


# Backward-compatible aliases for internal/private imports.
_ClientResolution = ClientResolution
_default_langfuse_client_factory = default_langfuse_client_factory
_resolve_client = resolve_client
