"""Pytest configuration and shared fixtures."""

import builtins
import sys
from collections.abc import Callable, Generator
from contextlib import AbstractContextManager
from pathlib import Path
from unittest.mock import patch

import pytest
from adaptix.load_error import ValidationLoadError

from dature.config import _ConfigProxy


def _collect_validation_errors(
    exc: BaseException,
    errors: list[ValidationLoadError],
) -> None:
    if isinstance(exc, ValidationLoadError):
        errors.append(exc)
    if hasattr(exc, "exceptions"):
        for sub_exc in exc.exceptions:
            _collect_validation_errors(sub_exc, errors)


@pytest.fixture
def collect_validation_errors() -> Callable[[BaseException], list[ValidationLoadError]]:
    def _collect(exc: BaseException) -> list[ValidationLoadError]:
        errors: list[ValidationLoadError] = []
        _collect_validation_errors(exc, errors)
        return errors

    return _collect


@pytest.fixture
def examples_dir() -> Path:
    """Return path to examples directory."""
    return Path(__file__).parent.parent / "examples"


@pytest.fixture
def fixtures_dir() -> Path:
    """Return path to fixtures directory."""
    return Path(__file__).parent / "fixtures"


# ENV fixtures
@pytest.fixture
def prefixed_env_file(fixtures_dir: Path) -> Path:
    """Path to .env file with APP_ prefix."""
    return fixtures_dir / "prefixed.env"


@pytest.fixture
def custom_separator_env_file(fixtures_dir: Path) -> Path:
    """Path to .env file with custom separator (dot instead of __)."""
    return fixtures_dir / "custom_separator.env"


@pytest.fixture
def all_types_env_file(examples_dir: Path) -> Path:
    """Path to all_types.env file."""
    return examples_dir / "sources" / "all_types.env"


# YAML fixtures
@pytest.fixture
def yaml_config_with_env_vars_file(fixtures_dir: Path) -> Path:
    """Path to YAML config file with environment variable substitution."""
    return fixtures_dir / "config_with_env_vars.yaml"


@pytest.fixture
def prefixed_yaml_file(fixtures_dir: Path) -> Path:
    """Path to YAML file with prefix."""
    return fixtures_dir / "prefixed.yaml"


@pytest.fixture
def all_types_yaml11_file(examples_dir: Path) -> Path:
    """Path to all_types YAML 1.1 file."""
    return examples_dir / "sources" / "all_types_yaml11.yaml"


@pytest.fixture
def all_types_yaml12_file(examples_dir: Path) -> Path:
    """Path to all_types YAML 1.2 file."""
    return examples_dir / "sources" / "all_types_yaml12.yaml"


# JSON fixtures
@pytest.fixture
def prefixed_json_file(fixtures_dir: Path) -> Path:
    """Path to JSON file with prefix."""
    return fixtures_dir / "prefixed.json"


@pytest.fixture
def all_types_json_file(examples_dir: Path) -> Path:
    """Path to all_types.json file."""
    return examples_dir / "sources" / "all_types.json"


# JSON5 fixtures
@pytest.fixture
def prefixed_json5_file(fixtures_dir: Path) -> Path:
    """Path to JSON5 file with prefix."""
    return fixtures_dir / "prefixed.json5"


@pytest.fixture
def all_types_json5_file(examples_dir: Path) -> Path:
    """Path to all_types.json5 file."""
    return examples_dir / "sources" / "all_types.json5"


# TOML fixtures
@pytest.fixture
def prefixed_toml_file(fixtures_dir: Path) -> Path:
    """Path to TOML file with prefix."""
    return fixtures_dir / "prefixed.toml"


@pytest.fixture
def all_types_toml10_file(examples_dir: Path) -> Path:
    """Path to all_types TOML 1.0 file."""
    return examples_dir / "sources" / "all_types_toml10.toml"


@pytest.fixture
def all_types_toml11_file(examples_dir: Path) -> Path:
    """Path to all_types TOML 1.1 file."""
    return examples_dir / "sources" / "all_types_toml11.toml"


@pytest.fixture
def array_of_tables_toml_file(fixtures_dir: Path) -> Path:
    return fixtures_dir / "array_of_tables.toml"


@pytest.fixture
def array_of_tables_error_first_toml_file(fixtures_dir: Path) -> Path:
    return fixtures_dir / "array_of_tables_error_first.toml"


@pytest.fixture
def array_of_tables_error_last_toml_file(fixtures_dir: Path) -> Path:
    return fixtures_dir / "array_of_tables_error_last.toml"


# INI fixtures
@pytest.fixture
def ini_sections_file(fixtures_dir: Path) -> Path:
    """Path to INI file with multiple sections and DEFAULT inheritance."""
    return fixtures_dir / "sections.ini"


@pytest.fixture
def prefixed_ini_file(fixtures_dir: Path) -> Path:
    """Path to INI file with prefix."""
    return fixtures_dir / "prefixed.ini"


@pytest.fixture
def all_types_ini_file(examples_dir: Path) -> Path:
    """Path to all_types.ini file."""
    return examples_dir / "sources" / "all_types.ini"


# Docker secrets fixtures
@pytest.fixture
def all_types_docker_secrets_dir(examples_dir: Path) -> Path:
    """Path to all_types_docker_secrets directory."""
    return examples_dir / "sources" / "all_types_docker_secrets"


@pytest.fixture
def _clean_dature_modules() -> Generator[None]:
    removed: dict[str, object] = {}
    for key in list(sys.modules):
        if key.startswith("dature."):
            removed[key] = sys.modules.pop(key)
    yield
    sys.modules.update(removed)


@pytest.fixture
def block_import(_clean_dature_modules: None) -> Callable[[str], AbstractContextManager[None]]:
    real_import = builtins.__import__

    def _block(module_name: str) -> AbstractContextManager[None]:
        def _blocker(name: str, *args: object, **kwargs: object) -> object:
            if name == module_name or name.startswith(module_name + "."):
                msg = f"No module named '{module_name}'"
                raise ImportError(msg)
            return real_import(name, *args, **kwargs)

        return patch("builtins.__import__", side_effect=_blocker)  # type: ignore[return-value]

    return _block


@pytest.fixture
def _reset_config() -> Generator[None]:
    _ConfigProxy.set_instance(None)
    yield
    _ConfigProxy.set_instance(None)
