"""
Shared fixtures for DeFiStream python-client integration tests.

Mirrors the API test suite conftest at api/tests/api-gateway/protocols/conftest.py,
adapted for the python-client's builder-pattern interface.

Run with:
    python -m pytest tests/test_integration.py -v
    python -m pytest tests/test_integration.py -v --local   # local dev gateway
"""

import os
import sys
import time
from pathlib import Path

import pytest

from defistream import AsyncDeFiStream, DeFiStream
from defistream.exceptions import ServerError

# Delay between integration tests to avoid triggering upstream RPC rate limits (429)
_INTER_TEST_DELAY = 5.0  # seconds

# Retry settings for transient 429 (rate limit) errors from upstream RPCs
_MAX_429_RETRIES = 3
_429_BACKOFF_BASE = 60.0  # seconds — each retry waits 60s

# ---------------------------------------------------------------------------
# --local flag support
# ---------------------------------------------------------------------------

_local_mode = "--local" in sys.argv

_PRODUCTION_URL = "https://api.defistream.dev/v1"
_LOCAL_URL = "http://localhost:8081/v1"


def pytest_addoption(parser):
    parser.addoption(
        "--local",
        action="store_true",
        default=False,
        help="Test against local dev gateway (http://localhost:8081/v1) instead of production",
    )


# ---------------------------------------------------------------------------
# .env loading
# ---------------------------------------------------------------------------

def _load_env_file(path: Path) -> dict[str, str]:
    if not path.exists():
        return {}
    env: dict[str, str] = {}
    for line in path.read_text(encoding="utf-8").splitlines():
        line = line.strip()
        if not line or line.startswith("#") or "=" not in line:
            continue
        key, value = line.split("=", 1)
        key = key.strip()
        value = value.strip()
        if (value.startswith('"') and value.endswith('"')) or (
            value.startswith("'") and value.endswith("'")
        ):
            value = value[1:-1]
        env[key] = value
    return env


def _get_env() -> dict[str, str]:
    env = dict(os.environ)
    project_root = Path(__file__).resolve().parents[1]  # python-client/
    repo_root = project_root.parent                      # DeFiStream/
    for candidate in [project_root / ".env", repo_root / ".env"]:
        for key, value in _load_env_file(candidate).items():
            if key not in env:
                env[key] = value
    return env


_env = _get_env()

# ---------------------------------------------------------------------------
# Resolved configuration
# ---------------------------------------------------------------------------

API_BASE_URL = _LOCAL_URL if _local_mode else _env.get("API_BASE_URL", _PRODUCTION_URL).rstrip("/")
TEST_API_KEY = _env.get("TEST_API_KEY", "")

# ---------------------------------------------------------------------------
# Auto-skip integration tests when API key is missing
# ---------------------------------------------------------------------------

_skip_no_key = pytest.mark.skipif(
    not TEST_API_KEY,
    reason="TEST_API_KEY not set in environment or .env",
)


def pytest_collection_modifyitems(config, items):
    """Skip integration tests automatically when TEST_API_KEY is absent."""
    for item in items:
        if "test_integration" in str(item.fspath):
            item.add_marker(_skip_no_key)


def _is_429_error(exc_info) -> bool:
    """Check if exception is a 429 rate-limit ServerError."""
    _, exc_value, _ = exc_info
    return isinstance(exc_value, ServerError) and exc_value.status_code == 429


@pytest.hookimpl(tryfirst=True)
def pytest_runtest_protocol(item, nextitem):
    """Auto-retry integration tests that fail with upstream 429 errors."""
    if "test_integration" not in str(item.fspath):
        return None  # let pytest handle non-integration tests normally

    from _pytest.runner import runtestprotocol

    for attempt in range(_MAX_429_RETRIES):
        reports = runtestprotocol(item, nextitem=nextitem, log=False)
        has_429 = False
        for report in reports:
            if report.failed and report.longrepr:
                # Check if the failure is a 429 ServerError
                longrepr_str = str(report.longrepr)
                if "429" in longrepr_str and "Too Many Requests" in longrepr_str:
                    has_429 = True
                    break

        if not has_429:
            # No 429 — report results normally and stop
            for report in reports:
                item.ihook.pytest_runtest_logreport(report=report)
            return True

        # 429 detected — wait with exponential backoff and retry
        backoff = _429_BACKOFF_BASE
        print(f"\n  ⏳ 429 rate limit on {item.name}, retrying in {backoff:.0f}s (attempt {attempt + 2}/{_MAX_429_RETRIES + 1})")
        time.sleep(backoff)

    # All retries exhausted — report the last attempt's results
    for report in reports:
        item.ihook.pytest_runtest_logreport(report=report)
    return True


# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------


@pytest.fixture(autouse=True)
def _throttle_integration_tests(request):
    """Add a small delay between integration tests to avoid upstream RPC 429s."""
    if "test_integration" in str(request.fspath):
        yield
        time.sleep(_INTER_TEST_DELAY)
    else:
        yield


@pytest.fixture(scope="module")
def client():
    """Sync DeFiStream client shared across a test module."""
    c = DeFiStream(api_key=TEST_API_KEY, base_url=API_BASE_URL)
    yield c
    c.close()


@pytest.fixture
async def async_client():
    """Async DeFiStream client — function-scoped for a clean event loop."""
    c = AsyncDeFiStream(api_key=TEST_API_KEY, base_url=API_BASE_URL)
    yield c
    await c.close()
