from __future__ import annotations

import os
from collections.abc import Callable, Mapping, Iterator
from pathlib import Path

import pytest

_ALLOWED_ENV_KEYS = {
    "CI",
    "CLICOLOR",
    "CLICOLOR_FORCE",
    "COLORTERM",
    "CURL_CA_BUNDLE",
    "GITHUB_ACTIONS",
    "HOME",
    "LANG",
    "LC_ALL",
    "LC_CTYPE",
    "LC_COLLATE",
    "LC_MESSAGES",
    "LOGNAME",
    "NODE_ENV",
    "NODE_OPTIONS",
    "NO_COLOR",
    "OLDPWD",
    "PATH",
    "PWD",
    "PYTEST_ADDOPTS",
    "PYTEST_CURRENT_TEST",
    "PYTEST_DISABLE_PLUGIN_AUTOLOAD",
    "PYTHONHOME",
    "PYTHONIOENCODING",
    "PYTHONPATH",
    "PYTHONUTF8",
    "PYTHONWARNINGS",
    "PYTHONDONTWRITEBYTECODE",
    "REQUESTS_CA_BUNDLE",
    "RUNNER_OS",
    "RUNNER_TEMP",
    "RUNNER_WORKSPACE",
    "SHELL",
    "SSL_CERT_DIR",
    "SSL_CERT_FILE",
    "TEMP",
    "TERM",
    "TMP",
    "TMPDIR",
    "TZ",
    "USER",
    "VIRTUAL_ENV",
}

_BASE_ENV = {key: os.environ[key] for key in os.environ if key in _ALLOWED_ENV_KEYS}
_ENV_FILE_NAMES = (".env.test.local", ".env.test")
_TEST_ENV_CACHE: dict[str, str] | None = None


def _repo_root() -> Path:
    return Path(__file__).resolve().parents[3]


def _find_env_file() -> Path | None:
    root = _repo_root()
    for name in _ENV_FILE_NAMES:
        candidate = root / name
        if candidate.is_file():
            return candidate
    return None


def _parse_env_file(path: Path) -> dict[str, str]:
    env: dict[str, str] = {}
    for raw_line in path.read_text().splitlines():
        line = raw_line.strip()
        if not line or line.startswith("#"):
            continue
        if line.startswith("export "):
            line = line[len("export ") :].lstrip()
        key, sep, value = line.partition("=")
        if not sep:
            continue
        key = key.strip()
        value = value.strip()
        if value and value[0] == value[-1] and value[0] in {'"', "'"}:
            value = value[1:-1]
        env[key] = value
    return env


def _load_test_env() -> dict[str, str]:
    global _TEST_ENV_CACHE
    if _TEST_ENV_CACHE is not None:
        return _TEST_ENV_CACHE
    path = _find_env_file()
    _TEST_ENV_CACHE = _parse_env_file(path) if path else {}
    return _TEST_ENV_CACHE


def _reset_env(extra: Mapping[str, str] | None = None) -> None:
    current_allowed = {
        key: os.environ[key] for key in os.environ if key in _ALLOWED_ENV_KEYS
    }
    os.environ.clear()
    os.environ.update(_BASE_ENV)
    os.environ.update(current_allowed)
    if extra:
        os.environ.update(extra)


_reset_env()


@pytest.fixture(autouse=True)
def _isolate_env() -> Iterator[None]:
    _reset_env()
    yield
    _reset_env()


@pytest.fixture
def load_env() -> Callable[..., dict[str, str]]:
    def _load(*keys: str) -> dict[str, str]:
        test_env = _load_test_env()
        loaded: dict[str, str] = {}
        for key in keys:
            if key in os.environ and os.environ[key] != "":
                loaded[key] = os.environ[key]
                continue
            if key in test_env:
                os.environ[key] = test_env[key]
                loaded[key] = test_env[key]
        return loaded

    return _load


@pytest.fixture
def require_env(
    load_env: Callable[..., dict[str, str]],
) -> Callable[..., dict[str, str]]:
    def _require(*keys: str) -> dict[str, str]:
        loaded = load_env(*keys)
        missing = [key for key in keys if key not in loaded or loaded[key] == ""]
        if missing:
            missing_list = ", ".join(missing)
            pytest.skip(
                "Missing test env vars: "
                f"{missing_list}. Define them in .env.test or .env.test.local at repo root."
            )
        return loaded

    return _require
