import importlib
import sys
import types

import pytest


def _build_stub(name: str, **attrs) -> types.ModuleType:
    module = types.ModuleType(name)
    for key, value in attrs.items():
        setattr(module, key, value)
    return module


class _DummyCursor:
    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc, tb):
        return False

    def execute(self, *_, **__):
        return None

    def fetchall(self):
        return []

    def fetchone(self):
        return None


class _DummyConnection:
    def cursor(self):
        return _DummyCursor()

    def close(self):
        return None

    def commit(self):
        return None

    def rollback(self):
        return None


def _install_dashboard_dependency_stubs(monkeypatch: pytest.MonkeyPatch) -> None:
    def _pass_through_decorator(func):
        return func

    class _DummyFlask:
        def __init__(self, *_args, static_folder=None, template_folder=None, **_kwargs):
            self.static_folder = static_folder
            self.template_folder = template_folder
            self.config = {}
            self.logger = types.SimpleNamespace(
                warning=lambda *_, **__: None,
                info=lambda *_, **__: None,
                error=lambda *_, **__: None,
                exception=lambda *_, **__: None,
            )

        def route(self, *_args, **_kwargs):
            return _pass_through_decorator

        def get(self, *_args, **_kwargs):
            return _pass_through_decorator

        def post(self, *_args, **_kwargs):
            return _pass_through_decorator

        def after_request(self, *_args, **_kwargs):
            return _pass_through_decorator

        def errorhandler(self, *_args, **_kwargs):
            return _pass_through_decorator

        def context_processor(self, *_args, **_kwargs):
            return _pass_through_decorator

        def template_filter(self, *_args, **_kwargs):
            return _pass_through_decorator

        def teardown_appcontext(self, *_args, **_kwargs):
            return _pass_through_decorator

        def before_request(self, *_args, **_kwargs):
            return _pass_through_decorator

    flask_module = _build_stub(
        "flask",
        Flask=_DummyFlask,
        render_template=lambda *_, **__: None,
        render_template_string=lambda *_, **__: None,
        request=None,
        redirect=lambda *_args, **_kwargs: None,
        url_for=lambda *_args, **_kwargs: "",
        Response=type("Response", (), {}),
        stream_with_context=lambda func: func,
        jsonify=lambda *_, **__: None,
        send_file=lambda *_, **__: None,
        g=types.SimpleNamespace(),
        abort=lambda *_args, **_kwargs: None,
    )
    monkeypatch.setitem(sys.modules, "flask", flask_module)

    werkzeug_exceptions = _build_stub(
        "werkzeug.exceptions", HTTPException=type("HTTPException", (Exception,), {})
    )
    werkzeug_utils = _build_stub(
        "werkzeug.utils", secure_filename=lambda filename: filename
    )
    werkzeug_datastructures = _build_stub(
        "werkzeug.datastructures", FileStorage=type("FileStorage", (), {})
    )
    monkeypatch.setitem(
        sys.modules,
        "werkzeug",
        _build_stub("werkzeug"),
    )
    monkeypatch.setitem(sys.modules, "werkzeug.exceptions", werkzeug_exceptions)
    monkeypatch.setitem(sys.modules, "werkzeug.utils", werkzeug_utils)
    monkeypatch.setitem(sys.modules, "werkzeug.datastructures", werkzeug_datastructures)

    jinja2_module = _build_stub(
        "jinja2",
        ChoiceLoader=type("ChoiceLoader", (), {"__init__": lambda self, *_, **__: None}),
        DictLoader=type("DictLoader", (), {"__init__": lambda self, *_, **__: None}),
    )
    monkeypatch.setitem(sys.modules, "jinja2", jinja2_module)

    requests_module = _build_stub(
        "requests",
        get=lambda *_, **__: None,
        post=lambda *_, **__: None,
        put=lambda *_, **__: None,
        delete=lambda *_, **__: None,
        Session=type("Session", (), {"__init__": lambda self, *_, **__: None}),
    )
    monkeypatch.setitem(sys.modules, "requests", requests_module)

    services_pkg = _build_stub("services", __path__=[])
    monkeypatch.setitem(sys.modules, "services", services_pkg)

    monkeypatch.setitem(
        sys.modules,
        "services.location_repository",
        _build_stub(
            "services.location_repository",
            LocationRepository=type("LocationRepository", (), {}),
            timestamp_str=lambda *_, **__: "",
        ),
    )

    class _DummyFailureNotifier:
        @classmethod
        def from_config(cls, *_args, **_kwargs):
            return cls()

        def notify(self, *_args, **_kwargs):
            return None

    monkeypatch.setitem(
        sys.modules,
        "services.ocpi_utils",
        _build_stub("services.ocpi_utils", FailureNotifier=_DummyFailureNotifier),
    )
    monkeypatch.setitem(
        sys.modules,
        "services.tariff_service",
        _build_stub(
            "services.tariff_service",
            TariffService=type(
                "TariffService",
                (),
                {
                    "__init__": lambda self, *_, **__: None,
                    "ensure_schema": lambda self: None,
                },
            ),
        ),
    )
    monkeypatch.setitem(
        sys.modules,
        "services.token_service",
        _build_stub(
            "services.token_service",
            TokenService=type("TokenService", (), {"__init__": lambda self, *_, **__: None}),
        ),
    )
    monkeypatch.setitem(
        sys.modules,
        "services.ocpi_validation",
        _build_stub(
            "services.ocpi_validation",
            DEFAULT_VERSION="2.2",
            SUPPORTED_MODULES=[],
            ValidationIssue=type("ValidationIssue", (), {}),
            normalize_version=lambda version: version,
            validate_payloads=lambda *_, **__: {},
        ),
    )
    monkeypatch.setitem(
        sys.modules,
        "hubject_client",
        _build_stub(
            "hubject_client",
            HubjectConfigurationError=type("HubjectConfigurationError", (Exception,), {}),
            load_hubject_config=lambda *_, **__: {},
        ),
    )
    monkeypatch.setitem(
        sys.modules,
        "ocpi_cdr_forwarder",
        _build_stub("ocpi_cdr_forwarder", build_cdr_endpoint=lambda *_, **__: None),
    )
    monkeypatch.setitem(
        sys.modules,
        "pymysql",
        _build_stub(
            "pymysql",
            connect=lambda *_, **__: _DummyConnection(),
            cursors=types.SimpleNamespace(DictCursor=object),
            converters=types.SimpleNamespace(escape_string=lambda value: value),
        ),
    )
    monkeypatch.setitem(
        sys.modules,
        "pymysql.converters",
        _build_stub("pymysql.converters", escape_string=lambda value: value),
    )
    monkeypatch.setitem(
        sys.modules,
        "pymysql.cursors",
        _build_stub("pymysql.cursors", DictCursor=object),
    )


@pytest.fixture()
def dashboard(monkeypatch: pytest.MonkeyPatch):
    monkeypatch.delitem(sys.modules, "pipelet_dashboard", raising=False)
    _install_dashboard_dependency_stubs(monkeypatch)
    module = importlib.import_module("pipelet_dashboard")
    yield module
    monkeypatch.delitem(sys.modules, "pipelet_dashboard", raising=False)


def test_collect_cp_ocpp_versions_uses_inbound_as_fallback(monkeypatch, dashboard):
    variants_seen: dict[str, set[str]] = {}

    def fake_lookup(variants):
        variants_seen["variants"] = variants
        return "2.0.1"

    monkeypatch.setattr(
        dashboard, "_lookup_inbound_ocpp_version", fake_lookup, raising=True
    )

    result = dashboard._collect_cp_ocpp_versions("CS001", "/CS001", None)

    assert result["inbound"] == "2.0.1"
    assert result["outbound"] == "2.0.1"
    assert "CS001" in variants_seen["variants"]
    assert "/CS001" in variants_seen["variants"]


def test_collect_cp_ocpp_versions_prefers_explicit_outbound(monkeypatch, dashboard):
    monkeypatch.setattr(
        dashboard, "_lookup_inbound_ocpp_version", lambda _: "1.6", raising=True
    )

    result = dashboard._collect_cp_ocpp_versions("CS001", "/CS001", "ocpp2.0.1")

    assert result["inbound"] == "1.6"
    assert result["outbound"] == "2.0.1"


def test_collect_cp_ocpp_versions_ignores_dash_outbound(monkeypatch):
    monkeypatch.setattr(
        dashboard, "_lookup_inbound_ocpp_version", lambda _: "2.0.1", raising=True
    )

    result = dashboard._collect_cp_ocpp_versions("CS001", "/CS001", "-")

    assert result["inbound"] == "2.0.1"
    assert result["outbound"] == "2.0.1"
