"""Shared pytest fixtures for django-o11y tests."""

import socket
import time

import pytest
from click.testing import CliRunner

# ---------------------------------------------------------------------------
# Integration fixture: full observability stack via Docker Compose
# ---------------------------------------------------------------------------

# Ports that must all be open before integration tests run.
# Alloy (:4317) is listed last because it depends_on all other services.
_STACK_PORTS = [
    ("localhost", 3000, "Grafana"),
    ("localhost", 9090, "Prometheus"),
    ("localhost", 3200, "Tempo"),
    ("localhost", 3100, "Loki"),
    ("localhost", 4040, "Pyroscope"),
    ("localhost", 12345, "Alloy UI"),
    ("localhost", 4317, "Alloy OTLP"),
]


def _port_open(host: str, port: int, timeout: float = 1.0) -> bool:
    """Return True if the TCP port accepts a connection."""
    try:
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.settimeout(timeout)
        s.connect((host, port))
        s.close()
        return True
    except OSError:
        return False


def _wait_for_stack(timeout: int = 120) -> bool:
    """Poll all stack ports until they are all open or timeout is reached."""
    deadline = time.time() + timeout
    while time.time() < deadline:
        if all(_port_open(host, port) for host, port, _ in _STACK_PORTS):
            return True
        time.sleep(3)
    return False


@pytest.fixture(scope="session")
def observability_stack():
    """Start the observability stack and wait until all services are ready.

    Tears the stack down after the session completes.  Any test that needs
    the stack should declare ``observability_stack`` as a parameter (or rely
    on the ``pytestmark`` set at module level).
    """
    from django_o11y.management.commands.o11y import cli

    runner = CliRunner()
    result = runner.invoke(cli, ["stack", "start"])

    if result.exit_code != 0:
        pytest.skip(
            f"Could not start observability stack (exit {result.exit_code}):\n"
            f"{result.output}"
        )

    ready = _wait_for_stack(timeout=120)
    if not ready:
        not_open = [
            f"{name} (:{port})"
            for host, port, name in _STACK_PORTS
            if not _port_open(host, port)
        ]
        runner.invoke(cli, ["stack", "stop"])
        pytest.skip(
            f"Observability stack did not become ready. Still waiting: {not_open}"
        )

    yield

    runner.invoke(cli, ["stack", "stop"])


@pytest.fixture
def mock_config():
    """Standard test config with all features enabled."""
    return {
        "SERVICE_NAME": "test-service",
        "ENVIRONMENT": "test",
        "NAMESPACE": "test-namespace",
        "TRACING": {
            "ENABLED": True,
            "OTLP_ENDPOINT": "http://localhost:4317",
            "SAMPLE_RATE": 1.0,
            "CONSOLE_EXPORTER": False,
        },
        "LOGGING": {
            "ENABLED": True,
            "FORMAT": "console",
            "LEVEL": "INFO",
            "COLORIZED": False,
            "RICH_EXCEPTIONS": False,
            "OTLP_ENABLED": False,
        },
        "METRICS": {
            "PROMETHEUS_ENABLED": True,
        },
        "CELERY": {
            "ENABLED": True,
        },
        "PROFILING": {
            "ENABLED": True,
            "PYROSCOPE_URL": "http://localhost:4040",
            "MODE": "push",
            "TAGS": {},
        },
        "RESOURCE_ATTRIBUTES": {},
        "CUSTOM_TAGS": {},
    }


@pytest.fixture
def celery_app():
    """Mock Celery app for testing."""
    try:
        from celery import Celery

        app = Celery("test-app", broker="memory://", backend="cache+memory://")
        app.conf.update(
            task_always_eager=True,
            task_eager_propagates=True,
        )
        return app
    except ImportError:
        return pytest.skip("Celery not installed")  # skip() raises Skipped


@pytest.fixture
def django_user_request(rf):
    """Request factory with authenticated user."""
    from django.contrib.auth.models import User

    request = rf.get("/")
    user = User(id=1, username="testuser", is_staff=False)
    user.pk = 1
    request.user = user

    return request


@pytest.fixture(autouse=True)
def reset_o11y_cache():
    """Reset the lru_cache on get_o11y_config for each test."""
    from django_o11y.conf import get_o11y_config

    get_o11y_config.cache_clear()
    yield
    get_o11y_config.cache_clear()
