"""
Integration framework.

Every integration subclasses ``Integration`` and implements
``setup_once()``.  The framework auto-discovers installed packages
and enables matching integrations, silently skipping unavailable ones.
"""

import threading
from abc import ABC, abstractmethod

from overcast_sdk.utils import capture_internal, debug_log, get_package_version

# ── Exceptions ────────────────────────────────────────────────────────────────

class DidNotEnable(Exception):
    """Raised by an integration that cannot be enabled.

    For *default* integrations this is silently swallowed.
    For *explicitly-requested* integrations it is re-raised.
    """


# ── Base class ────────────────────────────────────────────────────────────────

class Integration(ABC):
    """Abstract base for Overcast integrations."""

    identifier = ""  # unique string, e.g. "flask"

    @staticmethod
    @abstractmethod
    def setup_once():
        """Perform one-time setup (monkey-patching, hook registration)."""

    @classmethod
    def check_installed(cls, package_name, min_version=None):
        """Verify that *package_name* is installed.

        Raises ``DidNotEnable`` if the package is missing or below
        *min_version* (a tuple like ``(2, 0)``).
        """
        ver = get_package_version(package_name)
        if ver is None:
            raise DidNotEnable(f"{package_name} is not installed")
        if min_version and ver < min_version:
            raise DidNotEnable(
                f"{package_name} >= {'.'.join(str(v) for v in min_version)} "
                f"required, found {'.'.join(str(v) for v in ver)}"
            )


# ── Registry ──────────────────────────────────────────────────────────────────

_setup_lock = threading.Lock()
_installed = set()


def _get_default_integration_classes():
    """Lazily import integration classes so optional deps aren't loaded early."""
    from overcast_sdk.integrations._logging import LoggingIntegration
    from overcast_sdk.integrations._stdlib import StdlibIntegration
    from overcast_sdk.integrations._requests import RequestsIntegration
    from overcast_sdk.integrations._httpx import HttpxIntegration
    from overcast_sdk.integrations._flask import FlaskIntegration
    from overcast_sdk.integrations._django import DjangoIntegration
    from overcast_sdk.integrations._asgi import ASGIIntegration
    return [
        LoggingIntegration,
        StdlibIntegration,
        RequestsIntegration,
        HttpxIntegration,
        FlaskIntegration,
        DjangoIntegration,
        ASGIIntegration,
    ]


def setup_integrations(options, explicit=None):
    """Discover and set up all enabled integrations.

    *explicit* is a list of ``Integration`` **instances** the user passed.
    Default integrations are auto-discovered in addition.
    """
    explicit = list(explicit or [])
    explicit_ids = {i.identifier for i in explicit}

    # Auto-discovered defaults
    defaults = []
    for cls in _get_default_integration_classes():
        if cls.identifier not in explicit_ids:
            defaults.append(cls())

    all_integrations = explicit + defaults

    for integration in all_integrations:
        iid = integration.identifier
        with _setup_lock:
            if iid in _installed:
                continue
            try:
                type(integration).setup_once()
                _installed.add(iid)
                debug_log(f"Enabled integration: {iid}")
            except DidNotEnable as exc:
                if integration in explicit:
                    raise  # user explicitly requested — surface the error
                debug_log(f"Skipped integration {iid}: {exc}")
            except Exception:
                with capture_internal:
                    raise


def get_installed_integrations():
    """Return the set of currently-installed integration identifiers."""
    return set(_installed)
