"""Tests de structure du package cezam_lib."""

import ast
import importlib
from pathlib import Path

from hypothesis import given, settings
from hypothesis import strategies as st

BASE_DIR = Path(__file__).resolve().parent.parent / "src" / "cezam_lib"

CEZAM_SHARED_MODULES = [
    "__init__.py",
    "datalake_client.py",
    "datalake_paths.py",
    "exceptions.py",
    "minio_client.py",
    "otel.py",
    "rabbitmq.py",
    "source_client.py",
]

PIPELINE_TEMPLATE_MODULES = [
    "__init__.py",
    "base_pipeline.py",
    "extractor.py",
    "messages.py",
]


@given(module=st.sampled_from(CEZAM_SHARED_MODULES))
@settings(max_examples=100)
def test_all_cezam_shared_modules_exist(module: str) -> None:
    """Vérifie que chaque module attendu existe dans cezam_shared."""
    path = BASE_DIR / "cezam_shared" / module
    assert path.is_file(), f"Module manquant : {path}"


@given(module=st.sampled_from(PIPELINE_TEMPLATE_MODULES))
@settings(max_examples=100)
def test_all_pipeline_template_modules_exist(module: str) -> None:
    """Vérifie que chaque module attendu existe dans pipeline_template."""
    path = BASE_DIR / "pipeline_template" / module
    assert path.is_file(), f"Module manquant : {path}"


CEZAM_SHARED_EXPECTED_EXPORTS = [
    "MinIOClient",
    "DatalakeClient",
    "SourceClient",
    "datalake_paths",
    "MinIOError",
    "RabbitMQPublisher",
    "RabbitMQConsumer",
    "RabbitMQError",
    "RetryableError",
    "NonRetryableError",
    "setup_otel",
    "inject_trace_context",
    "extract_trace_context",
    "FormatAdapter",
    "__version__",
]

PIPELINE_TEMPLATE_EXPECTED_EXPORTS = [
    "BasePipeline",
    "DataExtractor",
    "FusionMessage",
    "PipelineMessage",
]


def _parse_all_from_init(init_path: Path) -> list[str]:
    """Parse __all__ depuis un fichier __init__.py via l'AST."""
    tree = ast.parse(init_path.read_text())
    for node in ast.walk(tree):
        if isinstance(node, ast.Assign):
            for target in node.targets:
                if isinstance(target, ast.Name) and target.id == "__all__":
                    if isinstance(node.value, ast.List):
                        return [
                            elt.value
                            for elt in node.value.elts
                            if isinstance(elt, ast.Constant)
                        ]
    return []


@given(export=st.sampled_from(CEZAM_SHARED_EXPECTED_EXPORTS))
@settings(max_examples=100)
def test_cezam_shared_exports_match_expected(export: str) -> None:
    """Vérifie que chaque export attendu est présent dans __all__ de cezam_shared."""
    actual_all = _parse_all_from_init(BASE_DIR / "cezam_shared" / "__init__.py")
    assert export in actual_all, f"Export manquant dans cezam_shared.__all__ : {export}"


@given(export=st.sampled_from(PIPELINE_TEMPLATE_EXPECTED_EXPORTS))
@settings(max_examples=100)
def test_pipeline_template_exports_match_expected(export: str) -> None:
    """Vérifie que chaque export attendu est présent dans __all__ de pipeline_template."""
    actual_all = _parse_all_from_init(BASE_DIR / "pipeline_template" / "__init__.py")
    assert export in actual_all, f"Export manquant dans pipeline_template.__all__ : {export}"


def test_cezam_shared_exports_set_identical() -> None:
    """Vérifie que l'ensemble des exports de cezam_shared est exactement celui attendu."""
    actual_all = _parse_all_from_init(BASE_DIR / "cezam_shared" / "__init__.py")
    expected = set(CEZAM_SHARED_EXPECTED_EXPORTS)
    actual = set(actual_all)
    assert actual == expected, (
        f"Différence dans cezam_shared.__all__ — "
        f"manquants: {expected - actual}, extras: {actual - expected}"
    )


def test_pipeline_template_exports_set_identical() -> None:
    """Vérifie que l'ensemble des exports de pipeline_template est exactement celui attendu."""
    actual_all = _parse_all_from_init(BASE_DIR / "pipeline_template" / "__init__.py")
    expected = set(PIPELINE_TEMPLATE_EXPECTED_EXPORTS)
    actual = set(actual_all)
    assert actual == expected, (
        f"Différence dans pipeline_template.__all__ — "
        f"manquants: {expected - actual}, extras: {actual - expected}"
    )


@given(symbol=st.sampled_from(CEZAM_SHARED_EXPECTED_EXPORTS))
@settings(max_examples=100, deadline=None)
def test_cezam_shared_exports_are_importable(symbol: str) -> None:
    """Vérifie que chaque export de cezam_shared est importable et non-None."""
    module = importlib.import_module("cezam_lib.cezam_shared")
    attr = getattr(module, symbol, None)
    assert attr is not None, f"L'export '{symbol}' est None ou absent dans cezam_lib.cezam_shared"


@given(symbol=st.sampled_from(PIPELINE_TEMPLATE_EXPECTED_EXPORTS))
@settings(max_examples=100, deadline=None)
def test_pipeline_template_exports_are_importable(symbol: str) -> None:
    """Vérifie que chaque export de pipeline_template est importable et non-None."""
    module = importlib.import_module("cezam_lib.pipeline_template")
    attr = getattr(module, symbol, None)
    assert attr is not None, f"L'export '{symbol}' est None ou absent dans cezam_lib.pipeline_template"
