"""Internal implementation of sluice.init()."""

from __future__ import annotations

import logging
import os
import re
import threading
from typing import Any

logger = logging.getLogger("sluice")

_SLUICE_API_KEY_ENV = "SLUICE_API_KEY"
_SLUICE_CONNECTION_ID_ENV = "SLUICE_CONNECTION_ID"
_UUID_RE = re.compile(
    r"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
    re.IGNORECASE,
)

_lock = threading.Lock()
_initialized: bool = False
_api_key: str = ""
_connection_id: str = ""


class SluiceConfigError(Exception):
    """Raised when the SDK is misconfigured.

    Error messages follow the Sluice format:
    [sluice] {what}. {why}. See: https://docs.sluice.sh/troubleshooting/{topic}

    Note: This exception is raised ONLY for developer configuration errors
    (missing API key, invalid setup). Runtime errors (network, API failures)
    are caught and logged — they never crash the Celery worker.
    """


def init(
    *,
    api_key: str | None = None,
    connection_id: str | None = None,
) -> None:
    """Initialize the Sluice Celery monitoring SDK.

    This is the only public API for V0. Call this once at application startup
    before Celery workers begin processing tasks.

    Installs a Celery Bootstep that:
    - Auto-configures ``worker_send_task_events``, ``task_send_sent_event``,
      and ``task_track_started`` to True.
    - Warns (but does not fail) if the user explicitly disabled those flags.
    - Never touches ``worker_hijack_root_logger``.
    - Auto-detects Django vs standalone Celery.

    Args:
        api_key: Your Sluice API key. Falls back to the ``SLUICE_API_KEY``
            environment variable if not provided.
        connection_id: Your Sluice connection UUID. Falls back to the
            ``SLUICE_CONNECTION_ID`` environment variable if not provided.
            Found in the Sluice dashboard under Connections.

    Raises:
        SluiceConfigError: If no API key or connection ID is provided.

    Example::

        import sluice

        sluice.init(
            api_key="sk_live_...",
            connection_id="550e8400-e29b-41d4-a716-446655440000",
        )

        # Or set SLUICE_API_KEY and SLUICE_CONNECTION_ID env vars and call:
        sluice.init()
    """
    global _initialized, _api_key, _connection_id  # noqa: PLW0603

    resolved_key = api_key or os.environ.get(_SLUICE_API_KEY_ENV, "")
    if not resolved_key.strip():
        raise SluiceConfigError(
            "[sluice] API key is empty. "
            "Pass api_key= or set SLUICE_API_KEY env var. "
            "See: https://docs.sluice.sh/troubleshooting/api-key"
        )

    resolved_conn_id = connection_id or os.environ.get(_SLUICE_CONNECTION_ID_ENV, "")
    if not resolved_conn_id.strip():
        raise SluiceConfigError(
            "[sluice] Connection ID is empty. "
            "Pass connection_id= or set SLUICE_CONNECTION_ID env var. "
            "Find your connection ID in the Sluice dashboard under Connections. "
            "See: https://docs.sluice.sh/troubleshooting/connection-id"
        )
    if not _UUID_RE.match(resolved_conn_id.strip()):
        raise SluiceConfigError(
            "[sluice] Connection ID is not a valid UUID. "
            f"Got: {resolved_conn_id.strip()!r}. "
            "Find your connection ID in the Sluice dashboard under Connections. "
            "See: https://docs.sluice.sh/troubleshooting/connection-id"
        )

    stripped_key = resolved_key.strip()
    stripped_conn_id = resolved_conn_id.strip()

    with _lock:
        if _initialized:
            logger.warning(
                "[sluice] init() called multiple times. "
                "Only the first call takes effect."
            )
            return

        # All runtime work below is wrapped in try/except.
        # The SDK runs in customer infrastructure. A bug here can take down
        # their payments queue. See: production-cannot-fail philosophy.
        try:
            _setup_celery_integration(stripped_key, stripped_conn_id)
        except Exception:
            logger.exception(
                "[sluice] Failed to set up Celery integration. "
                "Monitoring will not be active. "
                "See: https://docs.sluice.sh/troubleshooting/setup"
            )
            return

        _initialized = True
        _api_key = stripped_key
        _connection_id = stripped_conn_id

    logger.info("[sluice] SDK initialized successfully.")


def _setup_celery_integration(
    resolved_api_key: str,
    resolved_connection_id: str,
) -> None:
    """Wire the Sluice bootstep into the Celery app.

    Uses the ``celeryd_init`` signal to install the bootstep when the
    worker process starts. This works for both Django and standalone Celery.
    """
    from sluice._bootstep import install_bootstep
    from sluice._config import is_django_project

    if is_django_project():
        logger.info("[sluice] Django project detected.")
        _install_via_django(resolved_api_key, resolved_connection_id)
    else:
        logger.info("[sluice] Standalone Celery detected.")

    _connect_celeryd_init_signal(
        resolved_api_key, resolved_connection_id, install_bootstep
    )


def _install_via_django(
    resolved_api_key: str,
    resolved_connection_id: str,
) -> None:
    """Attempt to eagerly install the bootstep on a Django Celery app."""
    try:
        from celery import current_app

        app = current_app._get_current_object()
        if app is not None:
            from sluice._bootstep import install_bootstep

            install_bootstep(app, resolved_api_key, resolved_connection_id)
    except Exception:
        # Not fatal — the celeryd_init signal will catch it.
        logger.debug(
            "[sluice] Could not eagerly install bootstep on Django Celery app. "
            "Will install via celeryd_init signal."
        )


def _connect_celeryd_init_signal(
    resolved_api_key: str,
    resolved_connection_id: str,
    install_bootstep_fn: Any,
) -> None:
    """Connect to Celery's ``celeryd_init`` signal.

    This signal fires when a worker process starts, before it begins
    consuming tasks. The bootstep's ``start()`` method handles
    auto-configuration — we only need to install the bootstep here.
    """
    from celery.signals import celeryd_init

    @celeryd_init.connect(dispatch_uid="sluice_celeryd_init", weak=False)  # type: ignore[untyped-decorator]
    def _on_celeryd_init(sender: Any = None, **kwargs: Any) -> None:
        try:
            if sender is None:
                return

            from celery import current_app

            app = current_app._get_current_object()
            if app is None:
                return

            install_bootstep_fn(app, resolved_api_key, resolved_connection_id)

            logger.info(
                "[sluice] Celery worker '%s' configured for monitoring.",
                sender,
            )
        except Exception:
            # Production-cannot-fail: log and continue.
            logger.exception(
                "[sluice] Error during worker init signal handling. "
                "See: https://docs.sluice.sh/troubleshooting/setup"
            )


def _reset() -> None:
    """Reset SDK state. For testing only."""
    global _initialized, _api_key, _connection_id  # noqa: PLW0603

    from celery.signals import celeryd_init

    with _lock:
        _initialized = False
        _api_key = ""
        _connection_id = ""

    # Disconnect the signal handler so tests don't leak state.
    celeryd_init.disconnect(dispatch_uid="sluice_celeryd_init")
