"""Configuration helpers for the Sandai operator client."""

import os
from dataclasses import dataclass
from typing import Any, Dict, List, Optional

VALID_PRIORITIES = ("high", "normal", "low", "very_low")
_DEFAULT_BROKER_URL = "redis://localhost:6379/0"
_DEFAULT_ACCEPT_CONTENT = ["json", "msgpack"]
DEFAULT_BLOCKING_TIMEOUT_SECONDS = 3600


def _default_accept_content(task_serializer: str, result_serializer: str) -> List[str]:
    values = list(_DEFAULT_ACCEPT_CONTENT)
    for serializer in (task_serializer, result_serializer):
        if serializer and serializer not in values:
            values.append(serializer)
    return values


def env_bool(name: str, default: bool = False) -> bool:
    value = os.getenv(name)
    if value is None:
        return default
    return value.strip().lower() in {"1", "true", "yes", "on"}


def env_int(name: str, default: int) -> int:
    value = os.getenv(name)
    if value is None:
        return default
    try:
        return int(value.strip())
    except (TypeError, ValueError):
        return default


def env_float(name: str, default: float) -> float:
    value = os.getenv(name)
    if value is None:
        return default
    try:
        return float(value.strip())
    except (TypeError, ValueError):
        return default


@dataclass
class CeleryConfig:
    """Celery connection settings."""

    broker_url: str
    result_backend: str
    task_serializer: str = "msgpack"
    result_serializer: str = "json"
    accept_content: list | None = None
    broker_pool_limit: int = 20
    broker_transport_options: dict[str, Any] | None = None
    result_backend_transport_options: dict[str, Any] | None = None

    def __post_init__(self) -> None:
        if self.accept_content is None:
            self.accept_content = _default_accept_content(
                self.task_serializer,
                self.result_serializer,
            )

        if self.broker_transport_options is None:
            self.broker_transport_options = {
                "max_connections": self.broker_pool_limit,
            }
        if self.result_backend_transport_options is None:
            self.result_backend_transport_options = {
                "max_connections": self.broker_pool_limit,
            }


@dataclass(frozen=True)
class ClientConfig:
    """Resolved runtime configuration for ``OperatorClient``."""

    operator_name: str
    operator_version: str
    celery: CeleryConfig
    timeout: int = DEFAULT_BLOCKING_TIMEOUT_SECONDS
    debug: bool = False
    priority: str = "normal"
    cancel_key: Optional[str] = None
    auto_forget_result: bool = True
    broker_pool_limit: int = 12
    backend_pool_limit: int = 12
    backend_poll_concurrency: int = 6
    pool_acquire_timeout: float = 10.0
    thread_safe_mode: bool = True
    shared_transport_scope: str = "operator"
    detailed_debug: bool = False

    @property
    def broker_url(self) -> str:
        return self.celery.broker_url

    @property
    def result_backend(self) -> str:
        return self.celery.result_backend

    @property
    def task_name(self) -> str:
        return f"sandai_operator.{self.operator_name}.{self.operator_version}.process_task"

    @property
    def app_name(self) -> str:
        return f"sandai_client_{self.operator_name}_{self.operator_version}"

    @property
    def queue_names(self) -> Dict[str, str]:
        return {
            priority: f"{priority}.{self.operator_name}.{self.operator_version}"
            for priority in VALID_PRIORITIES
        }


def get_celery_config() -> Optional[CeleryConfig]:
    """Read Celery configuration from environment variables."""

    broker_url = os.getenv("SANDAI_OPERATOR_CELERY_BROKER_URL")
    result_backend = os.getenv("SANDAI_OPERATOR_CELERY_RESULT_BACKEND", broker_url)
    task_serializer = os.getenv("SANDAI_OPERATOR_CELERY_TASK_SERIALIZER", "msgpack")
    result_serializer = os.getenv(
        "SANDAI_OPERATOR_CELERY_RESULT_SERIALIZER", "json"
    )
    broker_pool_limit = env_int("SANDAI_OPERATOR_CELERY_BROKER_POOL_LIMIT", 20)
    backend_max_connections = env_int(
        "SANDAI_OPERATOR_CELERY_RESULT_BACKEND_MAX_CONNECTIONS",
        broker_pool_limit,
    )

    if not (broker_url and result_backend):
        return None

    return CeleryConfig(
        broker_url=broker_url,
        result_backend=result_backend,
        task_serializer=task_serializer,
        result_serializer=result_serializer,
        broker_pool_limit=broker_pool_limit,
        broker_transport_options={"max_connections": broker_pool_limit},
        result_backend_transport_options={"max_connections": backend_max_connections},
    )


def get_default_celery_config() -> CeleryConfig:
    """Return the local-development Celery defaults."""

    return CeleryConfig(
        broker_url=_DEFAULT_BROKER_URL,
        result_backend=_DEFAULT_BROKER_URL,
    )


def resolve_client_config(
    operator_name: str,
    operator_version: str,
    *,
    broker_url: Optional[str] = None,
    result_backend: Optional[str] = None,
    timeout: int = DEFAULT_BLOCKING_TIMEOUT_SECONDS,
    debug: bool = False,
    priority: str = "normal",
    cancel_key: Optional[str] = None,
    auto_forget_result: Optional[bool] = None,
    broker_pool_limit: Optional[int] = None,
    result_backend_max_connections: Optional[int] = None,
    backend_pool_limit: Optional[int] = None,
    backend_poll_concurrency: Optional[int] = None,
    pool_acquire_timeout: Optional[float] = None,
    thread_safe_mode: bool = True,
    shared_transport_scope: str = "operator",
) -> ClientConfig:
    """Resolve the effective client configuration without creating transport state."""

    if broker_url or result_backend:
        resolved_broker_url = broker_url or _DEFAULT_BROKER_URL
        resolved_result_backend = result_backend or resolved_broker_url
        resolved_broker_pool_limit = (
            broker_pool_limit
            if broker_pool_limit is not None
            else env_int("SANDAI_OPERATOR_CELERY_BROKER_POOL_LIMIT", 20)
        )
        resolved_backend_max_connections = (
            result_backend_max_connections
            if result_backend_max_connections is not None
            else env_int(
                "SANDAI_OPERATOR_CELERY_RESULT_BACKEND_MAX_CONNECTIONS",
                resolved_broker_pool_limit,
            )
        )
        celery = CeleryConfig(
            broker_url=resolved_broker_url,
            result_backend=resolved_result_backend,
            broker_pool_limit=resolved_broker_pool_limit,
            broker_transport_options={"max_connections": resolved_broker_pool_limit},
            result_backend_transport_options={
                "max_connections": resolved_backend_max_connections
            },
        )
    else:
        celery = get_celery_config() or get_default_celery_config()

    return ClientConfig(
        operator_name=operator_name,
        operator_version=operator_version,
        celery=celery,
        timeout=timeout,
        debug=debug,
        priority=priority,
        cancel_key=cancel_key,
        auto_forget_result=(
            auto_forget_result
            if auto_forget_result is not None
            else env_bool("SANDAI_OPERATOR_AUTO_FORGET_RESULT", True)
        ),
        broker_pool_limit=max(
            1,
            broker_pool_limit
            if broker_pool_limit is not None
            else env_int("SANDAI_OPERATOR_BROKER_POOL_LIMIT", 12),
        ),
        backend_pool_limit=max(
            1,
            backend_pool_limit
            if backend_pool_limit is not None
            else env_int("SANDAI_OPERATOR_BACKEND_POOL_LIMIT", 12),
        ),
        backend_poll_concurrency=max(
            1,
            backend_poll_concurrency
            if backend_poll_concurrency is not None
            else env_int("SANDAI_OPERATOR_BACKEND_POLL_CONCURRENCY", 6),
        ),
        pool_acquire_timeout=max(
            0.0,
            pool_acquire_timeout
            if pool_acquire_timeout is not None
            else env_float("SANDAI_OPERATOR_POOL_ACQUIRE_TIMEOUT", 10.0),
        ),
        thread_safe_mode=thread_safe_mode,
        shared_transport_scope=os.getenv(
            "SANDAI_OPERATOR_SHARED_TRANSPORT_SCOPE",
            shared_transport_scope,
        ),
        detailed_debug=env_bool("SANDAI_OPERATOR_CLIENT_DETAILED_DEBUG", False),
    )
