import asyncio
import logging
import sys
import types

import pytest

# Provide a lightweight pymysql stub for environments without the driver installed.
if "pymysql" not in sys.modules:
    fake_pymysql = types.ModuleType("pymysql")
    fake_pymysql.Warning = type("Warning", (Warning,), {})

    class _DummyDictCursor:  # pragma: no cover - minimal stub
        pass

    def _dummy_connect(*args, **kwargs):  # pragma: no cover - safety fallback
        raise AssertionError("pymysql.connect should be patched in tests")

    fake_pymysql.connect = _dummy_connect
    fake_cursors = types.ModuleType("pymysql.cursors")
    fake_cursors.DictCursor = _DummyDictCursor
    fake_pymysql.cursors = fake_cursors
    fake_err = types.ModuleType("pymysql.err")
    fake_err.OperationalError = type("OperationalError", (Exception,), {})
    fake_err.InterfaceError = type("InterfaceError", (Exception,), {})
    fake_pymysql.err = fake_err
    sys.modules["pymysql"] = fake_pymysql
    sys.modules["pymysql.cursors"] = fake_cursors
    sys.modules["pymysql.err"] = fake_err

if "aiohttp" not in sys.modules:
    fake_aiohttp = types.ModuleType("aiohttp")

    class _DummyClientSession:  # pragma: no cover - minimal stub
        def __init__(self, *args, **kwargs):
            pass

        async def __aenter__(self):
            return self

        async def __aexit__(self, exc_type, exc, tb):
            return False

    class _DummyClientTimeout:  # pragma: no cover - minimal stub
        def __init__(self, *args, **kwargs):
            pass

    class _DummyResponse:  # pragma: no cover - minimal stub
        def __init__(self, *args, **kwargs):
            self.args = args
            self.kwargs = kwargs

    class _DummyApplication:  # pragma: no cover - minimal stub
        def __init__(self, *args, **kwargs):
            self.routes = []

        def add_routes(self, routes):
            self.routes.append(routes)
            return None

    class _DummyRouteTableDef(list):  # pragma: no cover - minimal stub
        def get(self, *_args, **_kwargs):
            def _decorator(func):
                self.append(func)
                return func

            return _decorator

        def post(self, *_args, **_kwargs):
            def _decorator(func):
                self.append(func)
                return func

            return _decorator

    fake_web = types.SimpleNamespace(
        json_response=lambda *args, **kwargs: None,
        Request=object,
        Response=_DummyResponse,
        RouteTableDef=_DummyRouteTableDef,
        Application=_DummyApplication,
    )
    fake_aiohttp.ClientSession = _DummyClientSession
    fake_aiohttp.ClientTimeout = _DummyClientTimeout
    fake_aiohttp.web = fake_web
    sys.modules["aiohttp"] = fake_aiohttp
    sys.modules["aiohttp.web"] = fake_web

if "websockets" not in sys.modules:
    fake_websockets = types.ModuleType("websockets")
    fake_websockets_legacy = types.ModuleType("websockets.legacy")
    fake_websockets_server = types.ModuleType("websockets.legacy.server")
    fake_websockets_legacy_http = types.ModuleType("websockets.legacy.http")
    fake_websockets_exceptions = types.ModuleType("websockets.exceptions")

    class _DummyWebSocketException(Exception):  # pragma: no cover - minimal stub
        pass

    class _DummyInvalidMessage(_DummyWebSocketException):  # pragma: no cover
        pass

    class _DummyConnectionClosed(_DummyWebSocketException):  # pragma: no cover
        pass

    class _DummyWebSocketServerProtocol:  # pragma: no cover - minimal stub
        pass

    def _dummy_serve(*args, **kwargs):  # pragma: no cover - safety fallback
        raise AssertionError("websockets.serve should be patched in tests")

    async def _dummy_read_request(*args, **kwargs):  # pragma: no cover - safety fallback
        raise AssertionError("websockets.read_request should be patched in tests")

    fake_websockets_server.WebSocketServerProtocol = _DummyWebSocketServerProtocol
    fake_websockets_server.serve = _dummy_serve
    fake_websockets_legacy.server = fake_websockets_server
    fake_websockets_legacy.http = fake_websockets_legacy_http
    fake_websockets.legacy = fake_websockets_legacy
    fake_websockets.exceptions = fake_websockets_exceptions
    fake_websockets_exceptions.InvalidMessage = _DummyInvalidMessage
    fake_websockets_exceptions.WebSocketException = _DummyWebSocketException
    fake_websockets_exceptions.ConnectionClosed = _DummyConnectionClosed
    fake_websockets_legacy_http.read_request = _dummy_read_request

    sys.modules["websockets"] = fake_websockets
    sys.modules["websockets.legacy"] = fake_websockets_legacy
    sys.modules["websockets.legacy.server"] = fake_websockets_server
    sys.modules["websockets.legacy.http"] = fake_websockets_legacy_http
    sys.modules["websockets.exceptions"] = fake_websockets_exceptions

import pipelet_ocpp_server as server


def test_datatransfer_accepts_abb_vendor():
    status = asyncio.run(  # noqa: SLF001
        server._resolve_datatransfer_status(
            {"vendorId": "ABB", "messageId": "232", "data": {}},
            "CP-1",
        )
    )

    assert status == "Accepted"


def test_trigger_message_signv2gcertificate_accepts(monkeypatch):
    captured = {}

    async def fake_store(*args, **kwargs):
        captured["called"] = True

    monkeypatch.setattr(server, "store_pnc_csr", fake_store)

    status = asyncio.run(  # noqa: SLF001
        server._handle_iso15118_pnc_datatransfer(
            "CP-1",
            "TriggerMessage",
            {"requestedMessage": "SignV2GCertificate"},
            True,
            None,
        )
    )

    assert status == "Accepted"
    assert "called" not in captured


def test_trigger_message_unknown_requested_message_rejected_logs(caplog):
    caplog.set_level(logging.DEBUG)

    status = asyncio.run(  # noqa: SLF001
        server._handle_iso15118_pnc_datatransfer(
            "CP-1",
            "TriggerMessage",
            {"requestedMessage": "SomethingElse"},
            True,
            None,
        )
    )

    assert status == "Rejected"
    assert any("not supported" in rec.message for rec in caplog.records)


def test_trigger_message_invalid_payload_rejected_logs(caplog):
    caplog.set_level(logging.DEBUG)

    status = asyncio.run(  # noqa: SLF001
        server._handle_iso15118_pnc_datatransfer(
            "CP-1",
            "TriggerMessage",
            {},
            False,
            None,
        )
    )

    assert status == "Rejected"
    assert any("missing or invalid data" in rec.message for rec in caplog.records)


def test_sign_certificate_missing_csr_logs_and_accepts(monkeypatch, caplog):
    caplog.set_level(logging.DEBUG)
    captured = {}
    sign_requests: list[tuple] = []

    async def fake_store(chargepoint_id, csr, received_at=None):
        captured["params"] = (chargepoint_id, csr, received_at)

    async def fake_store_sign_request(chargepoint_id, ts=None, processed=0, payload=None):
        sign_requests.append((chargepoint_id, ts, processed, payload))

    monkeypatch.setattr(server, "store_pnc_csr", fake_store)
    monkeypatch.setattr(server, "store_cp_sign_request", fake_store_sign_request)

    status = asyncio.run(  # noqa: SLF001
        server._handle_iso15118_pnc_datatransfer(
            "CP-1",
            "SignCertificate",
            {},
            True,
            {"csr": "dummy"},
        )
    )

    assert status == "Accepted"
    assert sign_requests and sign_requests[0][0] == "CP-1"
    assert sign_requests[0][3] == '{"csr": "dummy"}'
    assert "params" not in captured
    assert any("missing csr" in rec.message for rec in caplog.records)
