from dataclasses import asdict

import pytest

from ocpp.charge_point import camel_to_snake_case, remove_nones, snake_to_camel_case
from ocpp.routing import create_route_map, on
from ocpp.v16.call import (
    BootNotificationPayload,
    GetConfigurationPayload,
    MeterValuesPayload,
)
from ocpp.v16.datatypes import MeterValue, SampledValue
from ocpp.v16.enums import Action
from ocpp.v20 import ChargePoint as cp
from ocpp.v201.call import SetNetworkProfilePayload
from ocpp.v201.datatypes import NetworkConnectionProfileType
from ocpp.v201.enums import OCPPInterfaceType, OCPPTransportType, OCPPVersionType


def test_getters_should_not_be_called_during_routemap_setup():
    class ChargePoint(cp):
        @property
        def foo(self):
            raise RuntimeError("this will be raised")

    try:
        ChargePoint("blah", None)
    except RuntimeError as e:
        assert str(e) == "this will be raised"
        pytest.fail("Getter was called during ChargePoint creation")


def test_multiple_classes_with_same_name_for_handler():
    class ChargerA(cp):
        @on(Action.Heartbeat)
        def heartbeat(self, **kwargs):
            pass

    class ChargerB(cp):
        @on(Action.Heartbeat)
        def heartbeat(self, **kwargs):
            pass

    A = ChargerA("A", None)
    B = ChargerB("B", None)
    route_mapA = create_route_map(A)
    route_mapB = create_route_map(B)
    assert route_mapA["Heartbeat"] != route_mapB["Heartbeat"]


@pytest.mark.parametrize(
    "test_input,expected",
    [
        ({"transactionId": "74563478"}, {"transaction_id": "74563478"}),
        ({"fullSoC": 100}, {"full_soc": 100}),
    ],
)
def test_camel_to_snake_case(test_input, expected):
    result = camel_to_snake_case(test_input)
    assert result == expected


@pytest.mark.parametrize(
    "test_input,expected",
    [
        ({"transaction_id": "74563478"}, {"transactionId": "74563478"}),
        ({"full_soc": 100}, {"fullSoC": 100}),
        ({"ev_min_v2x_energy_request": 200}, {"evMinV2XEnergyRequest": 200}),
        ({"v2x_charging_ctrlr": 200}, {"v2xChargingCtrlr": 200}),
    ],
)
def test_snake_to_camel_case(test_input, expected):
    result = snake_to_camel_case(test_input)
    assert result == expected


def test_remove_nones():
    expected_payload = {"charge_point_model": "foo", "charge_point_vendor": "bar"}

    payload = BootNotificationPayload(
        charge_point_model="foo",
        charge_point_vendor="bar",
        charge_box_serial_number=None,
    )
    payload = asdict(payload)

    assert expected_payload == remove_nones(payload)


def test_nested_remove_nones():
    expected_payload = {
        "configuration_slot": 1,
        "connection_data": {
            "ocpp_version": "OCPP20",
            "ocpp_transport": "JSON",
            "ocpp_csms_url": "wss://localhost:9000",
            "message_timeout": 60,
            "security_profile": 1,
            "ocpp_interface": "Wired0",
        },
    }

    connection_data = NetworkConnectionProfileType(
        ocpp_version=OCPPVersionType.ocpp20,
        ocpp_transport=OCPPTransportType.json,
        ocpp_csms_url="wss://localhost:9000",
        message_timeout=60,
        security_profile=1,
        ocpp_interface=OCPPInterfaceType.wired0,
        vpn=None,
        apn=None,
    )

    payload = SetNetworkProfilePayload(
        configuration_slot=1, connection_data=connection_data
    )
    payload = asdict(payload)

    assert expected_payload == remove_nones(payload)


def test_nested_list_remove_nones():
    expected_payload = {
        "connector_id": 3,
        "meter_value": [
            {
                "timestamp": "2017-08-17T07:08:06.186748+00:00",
                "sampled_value": [
                    {
                        "value": "10",
                        "context": "Sample.Periodic",
                        "measurand": "Power.Active.Import",
                        "unit": "W",
                    },
                    {
                        "value": "50000",
                        "context": "Sample.Periodic",
                        "measurand": "Power.Active.Import",
                        "phase": "L1",
                        "unit": "W",
                    },
                ],
            },
            {
                "timestamp": "2017-08-17T07:07:07.186748+00:00",
                "sampled_value": [
                    {
                        "value": "10",
                        "context": "Sample.Periodic",
                        "measurand": "Power.Active.Import",
                        "unit": "W",
                    },
                    {
                        "value": "50000",
                        "context": "Sample.Periodic",
                        "measurand": "Power.Active.Import",
                        "phase": "L1",
                        "unit": "W",
                    },
                ],
            },
        ],
        "transaction_id": 5,
    }

    payload = MeterValuesPayload(
        connector_id=3,
        meter_value=[
            MeterValue(
                timestamp="2017-08-17T07:08:06.186748+00:00",
                sampled_value=[
                    SampledValue(
                        value="10",
                        context="Sample.Periodic",
                        format=None,
                        measurand="Power.Active.Import",
                        phase=None,
                        location=None,
                        unit="W",
                    ),
                    SampledValue(
                        value="50000",
                        context="Sample.Periodic",
                        format=None,
                        measurand="Power.Active.Import",
                        phase="L1",
                        location=None,
                        unit="W",
                    ),
                ],
            ),
            MeterValue(
                timestamp="2017-08-17T07:07:07.186748+00:00",
                sampled_value=[
                    SampledValue(
                        value="10",
                        context="Sample.Periodic",
                        format=None,
                        measurand="Power.Active.Import",
                        phase=None,
                        location=None,
                        unit="W",
                    ),
                    SampledValue(
                        value="50000",
                        context="Sample.Periodic",
                        format=None,
                        measurand="Power.Active.Import",
                        phase="L1",
                        location=None,
                        unit="W",
                    ),
                ],
            ),
        ],
        transaction_id=5,
    )

    payload = asdict(payload)
    assert expected_payload == remove_nones(payload)


def test_remove_nones_with_list_of_strings():
    """Make sure that `remove_nones()` doesn't crash when it encounters an
    iterable other than a list or dict. See
    https://github.com/mobilityhouse/ocpp/issues/289.
    """
    payload = asdict(
        GetConfigurationPayload(key=["ClockAlignedDataInterval", "ConnectionTimeOut"])
    )

    assert remove_nones(payload) == {
        "key": ["ClockAlignedDataInterval", "ConnectionTimeOut"]
    }
