# SPDX-License-Identifier: Apache-2.0
# Copyright Pionix GmbH and Contributors to EVerest

import pytest
import asyncio
from datetime import datetime, timezone

import traceback
# fmt: off
import logging

from everest.testing.core_utils.controller.test_controller_interface import TestController

from ocpp.v201 import call as call201
from ocpp.v201 import call_result as call_result201
from ocpp.v201.enums import *
from ocpp.v201.datatypes import *
from ocpp.routing import on, create_route_map
from everest.testing.ocpp_utils.fixtures import *
from everest_test_utils import * # Needs to be before the datatypes below since it overrides the v201 Action enum with the v16 one
from ocpp.v201.enums import (Action, SetVariableStatusEnumType, ConnectorStatusEnumType,GetVariableStatusEnumType)
from validations import validate_status_notification_201, validate_notify_report_data_201, wait_for_callerror_and_validate
from everest.testing.core_utils._configuration.libocpp_configuration_helper import GenericOCPP2XConfigAdjustment
from everest.testing.ocpp_utils.charge_point_utils import wait_for_and_validate, TestUtility, OcppTestConfiguration, ValidationMode
from everest.testing.ocpp_utils.charge_point_v201 import ChargePoint201
# fmt: on

log = logging.getLogger("provisioningTest")


@pytest.mark.asyncio
@pytest.mark.ocpp_version("ocpp2.0.1")
async def test_B08_FR_07(
    central_system_v201: CentralSystem,
    test_controller: TestController,
    test_utility: TestUtility,
):
    """
    B08.FR.07
    ComponentCriteria contains: Active The Charging Station SHALL report every component that has
    the variable Active set to true, or does not have the Active variable in a NotifyReportRequest
    """

    log.info(
        " ############################# Test case B08: Get custom report ###############################"
    )

    test_controller.start()
    charge_point_v201 = await central_system_v201.wait_for_chargepoint(
        wait_for_bootnotification=True
    )

    # set a component variable to true
    r: call_result201.SetVariables = (
        await charge_point_v201.set_config_variables_req(
            "ChargingStatusIndicator", "Active", "true"
        )
    )
    set_variable_result: SetVariableResultType = SetVariableResultType(
        **r.set_variable_result[0]
    )
    assert set_variable_result.attribute_status == SetVariableStatusEnumType.accepted

    r = await charge_point_v201.get_report_req(
        request_id=567, component_criteria=[ComponentCriterionEnumType.active]
    )

    exp_single_report_data_active = ReportDataType(
        component=ComponentType(name="ChargingStatusIndicator"),
        variable=VariableType(name="Active"),
        variable_attribute=VariableAttributeType(
            type=AttributeEnumType.actual, value="true"
        ),
    )

    # get the value of component criteria
    assert await wait_for_and_validate(
        test_utility,
        charge_point_v201,
        "NotifyReport",
        call201.NotifyReport(
            request_id=567,
            generated_at=datetime.now().isoformat(),
            seq_no=0,
            report_data=[exp_single_report_data_active],
        ),
        validate_notify_report_data_201,
    )


@pytest.mark.asyncio
@pytest.mark.ocpp_version("ocpp2.0.1")
async def test_B08_FR_08(
    central_system_v201: CentralSystem,
    test_controller: TestController,
    test_utility: TestUtility,
):
    """
    B08.FR.08
    ComponentCriteria contains: Available The Charging Station SHALL report every component that has
    the variable Available set to true, or does not have the Available variable in a NotifyReportRequest
    """

    test_controller.start()
    charge_point_v201 = await central_system_v201.wait_for_chargepoint(
        wait_for_bootnotification=True
    )

    await charge_point_v201.get_report_req(
        request_id=777, component_criteria=[ComponentCriterionEnumType.available]
    )

    exp_single_report_data_avail = ReportDataType(
        component=ComponentType(name="AuthCacheCtrlr"),
        variable=VariableType(name="Available"),
        variable_attribute=VariableAttributeType(
            type=AttributeEnumType.actual,
            value="true",
            # mutability=MutabilityEnumType.read_write
        ),
    )

    # get the value of component criteria
    assert await wait_for_and_validate(
        test_utility,
        charge_point_v201,
        "NotifyReport",
        call201.NotifyReport(
            request_id=777,
            generated_at=datetime.now().isoformat(),
            seq_no=0,
            report_data=[exp_single_report_data_avail],
        ),
        validate_notify_report_data_201,
    )


@pytest.mark.asyncio
@pytest.mark.ocpp_version("ocpp2.0.1")
async def test_B08_FR_09(
    central_system_v201: CentralSystem,
    test_controller: TestController,
    test_utility: TestUtility,
):
    """B08.FR.09 ComponentCriteria contains: EnabledThe Charging Station SHALL report every component that
    has the variable Enabled set to true, or does not have the Enabled variable, in a NotifyReportRequest.
    """

    test_controller.start()
    charge_point_v201 = await central_system_v201.wait_for_chargepoint(
        wait_for_bootnotification=True
    )

    # Enable some variables with enable
    r: call_result201.SetVariables = (
        await charge_point_v201.set_config_variables_req(
            "AuthCacheCtrlr", "Enabled", "true"
        )
    )
    set_variable_result: SetVariableResultType = SetVariableResultType(
        **r.set_variable_result[0]
    )
    assert set_variable_result.attribute_status == SetVariableStatusEnumType.accepted

    r: call_result201.SetVariables = (
        await charge_point_v201.set_config_variables_req(
            "SampledDataCtrlr", "Enabled", "false"
        )
    )
    set_variable_result: SetVariableResultType = SetVariableResultType(
        **r.set_variable_result[0]
    )
    assert set_variable_result.attribute_status == SetVariableStatusEnumType.accepted

    r = await charge_point_v201.get_report_req(
        request_id=1,
        component_criteria=[ComponentCriterionEnumType.enabled],
        component_variable=[
            ComponentVariableType(component=ComponentType(name="TxCtrlr")),
            ComponentVariableType(
                component=ComponentType(name="DeviceDataCtrlr")),
            ComponentVariableType(
                component=ComponentType(name="AuthCacheCtrlr")),
        ],
    )

    exp_single_report_data = ReportDataType(
        component=ComponentType(name="AuthCacheCtrlr"),
        variable=VariableType(name="Enabled"),
        variable_attribute=VariableAttributeType(
            type=AttributeEnumType.actual,
            value="true",
            # mutability=MutabilityEnumType.read_write
        ),
        variable_characteristics=VariableCharacteristicsType(
            data_type=DataEnumType.boolean, supports_monitoring=True
        ),
    )

    # get the value of component criteria
    assert await wait_for_and_validate(
        test_utility,
        charge_point_v201,
        "NotifyReport",
        call201.NotifyReport(
            request_id=1,
            generated_at=datetime.now().isoformat(),
            seq_no=0,
            report_data=[exp_single_report_data],
        ),
        validate_notify_report_data_201,
    )

    # await asyncio.sleep(3)


@pytest.mark.asyncio
@pytest.mark.ocpp_version("ocpp2.0.1")
async def test_B08_FR_10(
    central_system_v201: CentralSystem,
    test_controller: TestController,
    test_utility: TestUtility,
):
    """
    B08.FR.10
    ComponentCriteria contains: ProblemThe Charging Station SHALL report every component that has
    the variable Problem set to true in a NotifyReportRequest.
    """

    test_controller.start()
    charge_point_v201 = await central_system_v201.wait_for_chargepoint(
        wait_for_bootnotification=True
    )

    # set a component variable to true
    r: call_result201.SetVariables = (
        await charge_point_v201.set_config_variables_req(
            "ChargingStation", "Problem", "true"
        )
    )
    set_variable_result: SetVariableResultType = SetVariableResultType(
        **r.set_variable_result[0]
    )
    assert set_variable_result.attribute_status == SetVariableStatusEnumType.accepted

    r = await charge_point_v201.get_report_req(
        request_id=45, component_criteria=[ComponentCriterionEnumType.problem]
    )

    exp_single_report_data2 = ReportDataType(
        component=ComponentType(name="ChargingStation"),
        variable=VariableType(name="Problem"),
        variable_attribute=VariableAttributeType(
            type=AttributeEnumType.actual,
            value="true",
            # mutability=MutabilityEnumType.read_write
        ),
    )

    # get the value of component criteria
    assert await wait_for_and_validate(
        test_utility,
        charge_point_v201,
        "NotifyReport",
        call201.NotifyReport(
            request_id=45,
            generated_at=datetime.now().isoformat(),
            seq_no=0,
            report_data=[exp_single_report_data2],
        ),
        validate_notify_report_data_201,
    )


@pytest.mark.asyncio
@pytest.mark.ocpp_version("ocpp2.0.1")
@pytest.mark.ocpp_config_adaptions(
    GenericOCPP2XConfigAdjustment(
        [
            (
                OCPP2XConfigVariableIdentifier(
                    "DeviceDataCtrlr", "BytesPerMessageGetReport", "Actual"
                ),
                "42",
            )
        ]
    )
)
async def test_B08_FR_18(
    central_system_v201: CentralSystem,
    test_controller: TestController,
    test_utility: TestUtility,
):
    """
    B08.FR.18
    Charging Station receives a GetReportRequest with a length of more bytes than allowed by BytesPerMessageGetReport
    The Charging Station MAY respond with a CALLERROR(FormatViolation)

    Setup: Set BytesPerMessage to 42 for this test
    """

    test_controller.start()
    charge_point_v201 = await central_system_v201.wait_for_chargepoint(
        wait_for_bootnotification=True
    )

    # get the value of BytesPerMessage
    r: call_result201.GetVariables = await charge_point_v201.get_variables_req(
        get_variable_data=[
            GetVariableDataType(
                component=ComponentType(name="DeviceDataCtrlr"),
                variable=VariableType(
                    name="BytesPerMessage",
                    instance="GetReport",
                ),
                attribute_type=AttributeEnumType.actual,
            )
        ]
    )
    get_variables_result: GetVariableResultType = GetVariableResultType(
        **r.get_variable_result[0]
    )
    assert get_variables_result.attribute_status == GetVariableStatusEnumType.accepted
    bytes_per_message = json.loads(get_variables_result.attribute_value)
    log.debug(" max bytes per get report request %d" % bytes_per_message)

    r = await charge_point_v201.get_report_req(
        request_id=777, component_criteria=[ComponentCriterionEnumType.available]
    )
    assert await wait_for_callerror_and_validate(
        test_utility, charge_point_v201, "FormatViolation"
    )


@pytest.mark.asyncio
@pytest.mark.ocpp_version("ocpp2.0.1")
@pytest.mark.ocpp_config_adaptions(
    GenericOCPP2XConfigAdjustment(
        [
            (
                OCPP2XConfigVariableIdentifier(
                    "DeviceDataCtrlr", "ItemsPerMessageGetReport"
                ),
                "2",
            )
        ]
    )
)
async def test_B08_FR_17(
    central_system_v201: CentralSystem,
    test_controller: TestController,
    test_utility: TestUtility,
):
    """
    B08.FR.17
    Charging Station receives a GetReportRequest with more ComponentVariableType elements than allowed by ItemsPerMessageGetReport
    The Charging Station MAY respond with a CALLERROR(OccurenceConstraintViolation)

    Setup set ItemsPerMessageGetReport to 2
    """

    test_controller.start()
    charge_point_v201 = await central_system_v201.wait_for_chargepoint(
        wait_for_bootnotification=True
    )

    # get the value of ItemsPerMessage
    r: call_result201.GetVariables = await charge_point_v201.get_variables_req(
        get_variable_data=[
            GetVariableDataType(
                component=ComponentType(name="DeviceDataCtrlr"),
                variable=VariableType(
                    name="ItemsPerMessage",
                    instance="GetReport",
                ),
                attribute_type=AttributeEnumType.actual,
            )
        ]
    )
    get_variables_result: GetVariableResultType = GetVariableResultType(
        **r.get_variable_result[0]
    )
    assert get_variables_result.attribute_status == GetVariableStatusEnumType.accepted
    items_per_message = json.loads(get_variables_result.attribute_value)
    log.debug(" max items per get report request %d" % items_per_message)

    r = await charge_point_v201.get_report_req(
        request_id=777,
        component_variable=[
            ComponentVariableType(
                component=ComponentType(name="TxCtrlr"),
                variable=VariableType(name="StopTxOnInvalidId"),
            ),
            ComponentVariableType(
                component=ComponentType(name="DeviceDataCtrlr"),
                variable=VariableType(
                    name="ItemsPerMessage",
                    instance="GetVariables",
                ),
            ),
            ComponentVariableType(
                component=ComponentType(name="AlignedDataCtrlr"),
                variable=VariableType(name="Measurands"),
            ),
        ],
    )
    assert await wait_for_callerror_and_validate(
        test_utility, charge_point_v201, "OccurenceConstraintViolation"
    )


@pytest.mark.asyncio
@pytest.mark.ocpp_version("ocpp2.0.1")
async def test_TC_B_18_CS(
    central_system_v201: CentralSystem,
    test_controller: TestController,
    test_utility: TestUtility,
):
    """
    TC_B_18_CS
    Get Custom Report - with component criteria and component/variable
    """
    test_controller.start()
    charge_point_v201 = await central_system_v201.wait_for_chargepoint(
        wait_for_bootnotification=True
    )

    r = await charge_point_v201.get_report_req(
        request_id=2534,
        component_criteria=[ComponentCriterionEnumType.available],
        component_variable=[
            ComponentVariableType(
                component=ComponentType(name="EVSE", evse=EVSEType(id=1)),
                variable=VariableType(name="AvailabilityState"),
            )
        ],
    )

    exp_single_report_data = ReportDataType(
        component=ComponentType(name="EVSE", evse=EVSEType(id=1)),
        variable=VariableType(name="AvailabilityState"),
        variable_attribute=VariableAttributeType(
            type=AttributeEnumType.actual, value="Available"
        ),
    )

    assert await wait_for_and_validate(
        test_utility,
        charge_point_v201,
        "NotifyReport",
        call201.NotifyReport(
            request_id=2534,
            generated_at=datetime.now().isoformat(),
            seq_no=0,
            report_data=[exp_single_report_data],
        ),
        validate_notify_report_data_201,
    )

    r: call_result201.GetReport = await charge_point_v201.get_report_req(
        request_id=2535,
        component_criteria=[ComponentCriterionEnumType.problem],
        component_variable=[
            ComponentVariableType(
                component=ComponentType(name="EVSE", evse=EVSEType(id=1)),
                variable=VariableType(name="AvailabilityState"),
            )
        ],
    )

    # should return an empty set
    assert r.status == "EmptyResultSet"


@pytest.mark.asyncio
@pytest.mark.ocpp_version("ocpp2.0.1")
@pytest.mark.ocpp_config_adaptions(
    GenericOCPP2XConfigAdjustment(
        [
            (
                OCPP2XConfigVariableIdentifier(
                    "DeviceDataCtrlr", "ItemsPerMessageGetReport"
                ),
                "4",
            ),
            (
                OCPP2XConfigVariableIdentifier(
                    "DeviceDataCtrlr", "ItemsPerMessageGetVariables"
                ),
                "2",
            ),
        ]
    )
)
async def test_TC_B_54_CS(
    central_system_v201: CentralSystem,
    test_controller: TestController,
    test_utility: TestUtility,
):
    """
    TC_B_54_CS
    Get Custom Report - with component/variable, but no instance
    """
    test_controller.start()
    charge_point_v201 = await central_system_v201.wait_for_chargepoint(
        wait_for_bootnotification=True
    )

    await charge_point_v201.get_report_req(
        request_id=2538,
        component_variable=[
            ComponentVariableType(
                component=ComponentType(
                    name="DeviceDataCtrlr",
                ),
                variable=VariableType(name="ItemsPerMessage"),
            )
        ],
    )

    b_54_1 = ReportDataType(
        component=ComponentType(
            name="DeviceDataCtrlr",
        ),
        variable=VariableType(name="ItemsPerMessage", instance="GetReport"),
        variable_attribute=VariableAttributeType(
            type=AttributeEnumType.actual, value="4"),
    )

    b_54_2 = ReportDataType(
        component=ComponentType(
            name="DeviceDataCtrlr",
        ),
        variable=VariableType(name="ItemsPerMessage", instance="GetVariables"),
        variable_attribute=VariableAttributeType(
            type=AttributeEnumType.actual, value="2"),
    )

    assert await wait_for_and_validate(
        test_utility,
        charge_point_v201,
        "NotifyReport",
        call201.NotifyReport(
            request_id=2538,
            generated_at=datetime.now().isoformat(),
            seq_no=0,
            report_data=[b_54_1, b_54_2],
        ),
        validate_notify_report_data_201,
    )


@pytest.mark.asyncio
@pytest.mark.ocpp_version("ocpp2.0.1")
@pytest.mark.ocpp_config_adaptions(
    GenericOCPP2XConfigAdjustment(
        [
            (
                OCPP2XConfigVariableIdentifier(
                    "DeviceDataCtrlr", "ItemsPerMessageGetReport"
                ),
                "4",
            )
        ]
    )
)
async def test_TC_B_55_CS(
    central_system_v201: CentralSystem,
    test_controller: TestController,
    test_utility: TestUtility,
):
    """
    TC_B_55_CS
    Get Custom Report - with component/variable/instance
    """

    test_controller.start()
    charge_point_v201 = await central_system_v201.wait_for_chargepoint(
        wait_for_bootnotification=True
    )

    await charge_point_v201.get_report_req(
        request_id=2539,
        component_variable=[
            ComponentVariableType(
                component=ComponentType(
                    name="DeviceDataCtrlr",
                ),
                variable=VariableType(
                    name="ItemsPerMessage", instance="GetReport"),
            )
        ],
    )

    b_55_1 = ReportDataType(
        component=ComponentType(
            name="DeviceDataCtrlr",
        ),
        variable=VariableType(name="ItemsPerMessage", instance="GetReport"),
        variable_attribute=VariableAttributeType(
            type=AttributeEnumType.actual, value="4"),
    )

    assert await wait_for_and_validate(
        test_utility,
        charge_point_v201,
        "NotifyReport",
        call201.NotifyReport(
            request_id=2539,
            generated_at=datetime.now().isoformat(),
            seq_no=0,
            report_data=[b_55_1],
        ),
        validate_notify_report_data_201,
    )


@pytest.mark.asyncio
@pytest.mark.ocpp_version("ocpp2.0.1")
async def test_TC_B_56_CS(
    central_system_v201: CentralSystem,
    test_controller: TestController,
    test_utility: TestUtility,
):
    """
    TC_B_56_CS
    Get Custom Report - with component/variable, but no evseId
    """

    test_controller.start()
    charge_point_v201 = await central_system_v201.wait_for_chargepoint(
        wait_for_bootnotification=True
    )

    await charge_point_v201.get_report_req(
        request_id=2544,
        component_variable=[
            ComponentVariableType(
                component=ComponentType(name="EVSE"),
                variable=VariableType(name="AvailabilityState"),
            )
        ],
    )

    b_56_1 = ReportDataType(
        component=ComponentType(name="EVSE", evse=EVSEType(id=1)),
        variable=VariableType(name="AvailabilityState"),
        variable_attribute=VariableAttributeType(
            type=AttributeEnumType.actual, value="Available"
        ),
    )

    b_56_2 = ReportDataType(
        component=ComponentType(name="EVSE", evse=EVSEType(id=2)),
        variable=VariableType(name="AvailabilityState"),
        variable_attribute=VariableAttributeType(
            type=AttributeEnumType.actual, value="Available"
        ),
    )

    assert await wait_for_and_validate(
        test_utility,
        charge_point_v201,
        "NotifyReport",
        call201.NotifyReport(
            request_id=2544,
            generated_at=datetime.now().isoformat(),
            seq_no=0,
            report_data=[b_56_1, b_56_2],
        ),
        validate_notify_report_data_201,
    )


@pytest.mark.asyncio
@pytest.mark.ocpp_version("ocpp2.0.1")
async def test_cold_boot_01(
    central_system_v201: CentralSystem,
    test_controller: TestController,
    test_utility: TestUtility,
):
    """
    B01.FR.01
    ...
    """

    test_controller.start()
    charge_point_v201 = await central_system_v201.wait_for_chargepoint()

    try:
        # expect StatusNotification with status available
        assert await wait_for_and_validate(
            test_utility,
            charge_point_v201,
            "StatusNotification",
            call201.StatusNotification(
                datetime.now().isoformat(), ConnectorStatusEnumType.available, 1, 1
            ),
            validate_status_notification_201,
        )
    except Exception as e:
        traceback.print_exc()
        logging.critical(e)

    # TOOD(piet): Check configured HeartbeatInterval of BootNotificationResponse


@pytest.mark.asyncio
@pytest.mark.ocpp_version("ocpp2.0.1")
async def test_cold_boot_pending_01(
    test_config: OcppTestConfiguration,
    central_system_v201: CentralSystem,
    test_controller: TestController,
    test_utility: TestUtility,
):

    @on(Action.boot_notification)
    def on_boot_notification_pending(**kwargs):
        return call_result201.BootNotification(
            current_time=datetime.now().isoformat(),
            interval=5,
            status=RegistrationStatusEnumType.pending,
        )

    @on(Action.boot_notification)
    def on_boot_notification_accepted(**kwargs):
        return call_result201.BootNotification(
            current_time=datetime.now(timezone.utc).isoformat(),
            interval=5,
            status=RegistrationStatusEnumType.accepted,
        )

    test_utility.forbidden_actions.append("SecurityEventNotification")

    central_system_v201.function_overrides.append(
        ("on_boot_notification", on_boot_notification_pending)
    )

    test_controller.start()
    charge_point_v201 = await central_system_v201.wait_for_chargepoint()

    setattr(charge_point_v201, "on_boot_notification",
            on_boot_notification_accepted)
    central_system_v201.chargepoint.route_map = create_route_map(
        central_system_v201.chargepoint
    )

    assert await wait_for_and_validate(
        test_utility, charge_point_v201, "BootNotification", {}
    )

    test_utility.forbidden_actions.clear()

    test_controller.plug_in()

    assert await wait_for_and_validate(
        test_utility, charge_point_v201, "SecurityEventNotification", {}
    )


@pytest.mark.asyncio
@pytest.mark.ocpp_version("ocpp2.0.1")
async def test_cold_boot_rejected_01(
    test_config: OcppTestConfiguration,
    central_system_v201: CentralSystem,
    test_controller: TestController,
    test_utility: TestUtility,
):

    @on(Action.boot_notification)
    def on_boot_notification_pending(**kwargs):
        return call_result201.BootNotification(
            current_time=datetime.now().isoformat(),
            interval=5,
            status=RegistrationStatusEnumType.rejected,
        )

    @on(Action.boot_notification)
    def on_boot_notification_accepted(**kwargs):
        return call_result201.BootNotification(
            current_time=datetime.now(timezone.utc).isoformat(),
            interval=5,
            status=RegistrationStatusEnumType.accepted,
        )

    central_system_v201.function_overrides.append(
        ("on_boot_notification", on_boot_notification_pending)
    )

    test_controller.start()
    charge_point_v201 = await central_system_v201.wait_for_chargepoint()

    setattr(charge_point_v201, "on_boot_notification",
            on_boot_notification_accepted)
    central_system_v201.chargepoint.route_map = create_route_map(
        central_system_v201.chargepoint
    )

    assert await wait_for_and_validate(
        test_utility, charge_point_v201, "BootNotification", {}
    )


@pytest.mark.asyncio
@pytest.mark.ocpp_version("ocpp2.0.1")
async def test_set_get_variables_01(
    charge_point_v201: ChargePoint201, test_utility: TestUtility
):

    await charge_point_v201.get_variables_req(
        get_variable_data=[
            GetVariableDataType(
                component=ComponentType(name="TxCtrlr"),
                variable=VariableType(name="StopTxOnInvalidId"),
                attribute_type=AttributeEnumType.actual,
            )
        ]
    )

    assert await wait_for_and_validate(
        test_utility,
        charge_point_v201,
        "GetVariables",
        call_result201.GetVariables(
            get_variable_result=[
                GetVariableResultType(
                    component=ComponentType(name="TxCtrlr"),
                    variable=VariableType(name="StopTxOnInvalidId"),
                    attribute_status=GetVariableStatusEnumType.accepted,
                    attribute_type=AttributeEnumType.actual,
                    attribute_value="true",
                )
            ]
        ),
    )

    await charge_point_v201.set_variables_req(
        set_variable_data=[
            SetVariableDataType(
                attribute_value="false",
                attribute_type=AttributeEnumType.actual,
                component=ComponentType(name="TxCtrlr"),
                variable=VariableType(name="StopTxOnInvalidId"),
            )
        ]
    )

    await charge_point_v201.get_variables_req(
        get_variable_data=[
            GetVariableDataType(
                component=ComponentType(name="TxCtrlr"),
                variable=VariableType(name="StopTxOnInvalidId"),
                attribute_type=AttributeEnumType.actual,
            )
        ]
    )

    assert await wait_for_and_validate(
        test_utility,
        charge_point_v201,
        "GetVariables",
        call_result201.GetVariables(
            get_variable_result=[
                GetVariableResultType(
                    component=ComponentType(name="TxCtrlr"),
                    variable=VariableType(name="StopTxOnInvalidId"),
                    attribute_status=GetVariableStatusEnumType.accepted,
                    attribute_type=AttributeEnumType.actual,
                    attribute_value="false",
                )
            ]
        ),
    )


@pytest.mark.asyncio
@pytest.mark.ocpp_version("ocpp2.0.1")
async def test_set_get_variables_02(
    charge_point_v201: ChargePoint201, test_utility: TestUtility
):
    await charge_point_v201.get_variables_req(
        get_variable_data=[
            GetVariableDataType(
                component=ComponentType(name="TxCtrlr"),
                variable=VariableType(name="StopTxOnInvalidId"),
                attribute_type=AttributeEnumType.actual,
            ),
            GetVariableDataType(
                component=ComponentType(name="InternalCtrlr"),
                variable=VariableType(name="ChargePointVendor"),
                attribute_type=AttributeEnumType.actual,
            ),
            GetVariableDataType(
                component=ComponentType(name="OCPPCommCtrlr"),
                variable=VariableType(name="UnknownVariable"),
                attribute_type=AttributeEnumType.actual,
            ),
            GetVariableDataType(
                component=ComponentType(name="UnknownComponent"),
                variable=VariableType(name="UnknownVariable"),
                attribute_type=AttributeEnumType.actual,
            ),
        ]
    )

    assert await wait_for_and_validate(
        test_utility,
        charge_point_v201,
        "GetVariables",
        call_result201.GetVariables(
            get_variable_result=[
                GetVariableResultType(
                    component=ComponentType(name="TxCtrlr"),
                    variable=VariableType(name="StopTxOnInvalidId"),
                    attribute_status=GetVariableStatusEnumType.accepted,
                    attribute_type=AttributeEnumType.actual,
                    attribute_value="true",
                ),
                GetVariableResultType(
                    component=ComponentType(name="InternalCtrlr"),
                    variable=VariableType(name="ChargePointVendor"),
                    attribute_status=GetVariableStatusEnumType.accepted,
                    attribute_type=AttributeEnumType.actual,
                    attribute_value="EVerestVendor",
                ),
                GetVariableResultType(
                    component=ComponentType(name="OCPPCommCtrlr"),
                    variable=VariableType(name="UnknownVariable"),
                    attribute_status=GetVariableStatusEnumType.unknown_variable,
                    attribute_type=AttributeEnumType.actual,
                ),
                GetVariableResultType(
                    component=ComponentType(name="UnknownComponent"),
                    variable=VariableType(name="UnknownVariable"),
                    attribute_status=GetVariableStatusEnumType.unknown_component,
                    attribute_type=AttributeEnumType.actual,
                ),
            ]
        ),
    )


@pytest.mark.asyncio
@pytest.mark.ocpp_version("ocpp2.0.1")
async def test_get_base_report_01(
    charge_point_v201: ChargePoint201, test_utility: TestUtility
):
    await charge_point_v201.get_base_report_req(
        request_id=1, report_base=ReportBaseEnumType.full_inventory
    )

    await wait_for_and_validate(
        test_utility,
        charge_point_v201,
        "GetBaseReport",
        call_result201.GetBaseReport(
            status=GenericDeviceModelStatusEnumType.accepted
        ),
    )

    exp_single_report_data = ReportDataType(
        component=ComponentType(name="TxCtrlr"),
        variable=VariableType(name="StopTxOnInvalidId"),
        variable_attribute=VariableAttributeType(
            type=AttributeEnumType.actual,
            value="true",
            mutability=MutabilityEnumType.read_write,
        ),
        variable_characteristics=VariableCharacteristicsType(
            data_type=DataEnumType.boolean, supports_monitoring=True
        ),
    )

    assert await wait_for_and_validate(
        test_utility,
        charge_point_v201,
        "NotifyReport",
        call201.NotifyReport(
            request_id=1,
            generated_at=datetime.now().isoformat(),
            seq_no=0,
            report_data=[exp_single_report_data],
        ),
        validate_notify_report_data_201,
    )


@pytest.mark.asyncio
@pytest.mark.ocpp_version("ocpp2.0.1")
async def test_get_custom_report_01(charge_point_v201: ChargePoint201):
    await charge_point_v201.get_report_req(
        request_id=1,
        component_variable=[
            ComponentVariableType(
                component=ComponentType(name="TxCtrlr"),
                variable=VariableType(name="NotAValidVariable"),
            ),
        ],
        component_criteria=[ComponentCriterionEnumType.enabled],
    )


@pytest.mark.asyncio
@pytest.mark.ocpp_version("ocpp2.0.1")
async def test_b09_b10(
    charge_point_v201: ChargePoint201,
    test_controller: TestController,
    central_system_v201: CentralSystem,
):

    # Fetch NetworkConfiguration variables instead of NetworkConnectionProfiles
    # Get initial NetworkConfiguration slot 1 variables
    r: call_result201.GetVariables = (
        await charge_point_v201.get_variables_req(
            get_variable_data=[
                GetVariableDataType(
                    component=ComponentType(name="NetworkConfiguration", instance="1"),
                    variable=VariableType(name="OcppCsmsUrl"),
                    attribute_type=AttributeEnumType.actual,
                )
            ]
        )
    )
    get_variables_result: GetVariableResultType = GetVariableResultType(
        **r.get_variable_result[0]
    )
    assert get_variables_result.attribute_status == GetVariableStatusEnumType.accepted
    # invalid security profile
    r: call_result201.SetNetworkProfile = (
        await charge_point_v201.set_network_profile_req(
            configuration_slot=1,
            connection_data=NetworkConnectionProfileType(
                ocpp_version=OCPPVersionEnumType.ocpp20,
                ocpp_transport=OCPPTransportEnumType.json,
                ocpp_csms_url="ws://localhost:9000/cp001",
                message_timeout=30,
                security_profile=0,
                ocpp_interface=OCPPInterfaceEnumType.wired0,
            ),
        )
    )

    assert r.status == "Rejected"

    # invalid configuration slot
    r: call_result201.SetNetworkProfile = (
        await charge_point_v201.set_network_profile_req(
            configuration_slot=100,
            connection_data=NetworkConnectionProfileType(
                ocpp_version=OCPPVersionEnumType.ocpp20,
                ocpp_transport=OCPPTransportEnumType.json,
                ocpp_csms_url="ws://localhost:9000/cp001",
                message_timeout=30,
                security_profile=0,
                ocpp_interface=OCPPInterfaceEnumType.wired0,
            ),
        )
    )

    assert r.status == "Rejected"

    # valid
    r: call_result201.SetNetworkProfile = (
        await charge_point_v201.set_network_profile_req(
            configuration_slot=2,
            connection_data=NetworkConnectionProfileType(
                ocpp_version=OCPPVersionEnumType.ocpp20,
                ocpp_transport=OCPPTransportEnumType.json,
                ocpp_csms_url="wss://localhost:9000/cp001",
                message_timeout=30,
                security_profile=2,
                ocpp_interface=OCPPInterfaceEnumType.wired0,
            ),
        )
    )

    assert r.status == "Accepted"

    # Check that NetworkConfiguration slot 2 now exists and has the expected values
    r: call_result201.GetVariables = (
        await charge_point_v201.get_variables_req(
            get_variable_data=[
                GetVariableDataType(
                    component=ComponentType(name="NetworkConfiguration", instance="2"),
                    variable=VariableType(name="OcppCsmsUrl"),
                    attribute_type=AttributeEnumType.actual,
                ),
                GetVariableDataType(
                    component=ComponentType(name="NetworkConfiguration", instance="2"),
                    variable=VariableType(name="SecurityProfile"),
                    attribute_type=AttributeEnumType.actual,
                )
            ]
        )
    )
    # Should get two successful results for slot 2 variables
    assert len(r.get_variable_result) == 2
    slot2_url_result = GetVariableResultType(**r.get_variable_result[0])
    slot2_security_result = GetVariableResultType(**r.get_variable_result[1])
    assert slot2_url_result.attribute_status == GetVariableStatusEnumType.accepted
    assert slot2_security_result.attribute_status == GetVariableStatusEnumType.accepted
    assert slot2_url_result.attribute_value == "wss://localhost:9000/cp001"
    assert slot2_security_result.attribute_value == "2"

    # Set valid NetworkConfigurationPriority
    r: call_result201.SetVariables = (
        await charge_point_v201.set_config_variables_req(
            "OCPPCommCtrlr", "NetworkConfigurationPriority", "2,1"
        )
    )
    set_variable_result: SetVariableResultType = SetVariableResultType(
        **r.set_variable_result[0]
    )
    assert set_variable_result.attribute_status == SetVariableStatusEnumType.accepted


@pytest.mark.asyncio
@pytest.mark.ocpp_version("ocpp2.0.1")
@pytest.mark.ocpp_config_adaptions(
    GenericOCPP2XConfigAdjustment(
        [
            (
                OCPP2XConfigVariableIdentifier(
                    "DeviceDataCtrlr", "ItemsPerMessageGetVariables"
                ),
                "2",
            )
        ]
    )
)
async def test_B06_09_16(
    central_system_v201: CentralSystem,
    test_controller: TestController,
    test_utility: TestUtility,
):
    """
    B06.FR.09
    B06.FR.16
    """
    log.info(
        " ############################# Test case B06: Get variables Request ###############################"
    )

    # When the Charging Station receives a GetVariablesRequest for a Variable in the GetVariableData that is WriteOnly,
    # The Charging Station SHALL set the attributeStatus field in the
    # corresponding GetVariableResult to: Rejected.

    test_controller.start()
    charge_point_v201 = await central_system_v201.wait_for_chargepoint(
        wait_for_bootnotification=True
    )

    # Write into Basic Auth Password
    r: call_result.SetVariables = (
        await charge_point_v201.set_config_variables_req(
            "SecurityCtrlr", "BasicAuthPassword", "8BADF00D8BADF00D"
        )
    )
    set_variable_result: SetVariableResultType = SetVariableResultType(
        **r.set_variable_result[0]
    )
    assert set_variable_result.attribute_status == SetVariableStatusEnumType.accepted

    # wait for reconnect
    charge_point_v201 = await central_system_v201.wait_for_chargepoint(
        wait_for_bootnotification=False
    )

    r: call_result201.GetVariables = (
        await charge_point_v201.get_config_variables_req(
            "SecurityCtrlr", "BasicAuthPassword"
        )
    )
    get_variables_result: GetVariableResultType = GetVariableResultType(
        **r.get_variable_result[0]
    )
    assert get_variables_result.attribute_status == GetVariableStatusEnumType.rejected

    # Charging Station receives a GetVariablesRequest with more GetVariableData elements than allowed by ItemsPerMessageGetVariables
    # The Charging Station MAY respond with a CALLERROR(OccurenceConstraintViolation)

    # get the value of ItemsPerMessage
    r: call_result201.GetVariables = await charge_point_v201.get_variables_req(
        get_variable_data=[
            GetVariableDataType(
                component=ComponentType(name="DeviceDataCtrlr"),
                variable=VariableType(
                    name="ItemsPerMessage",
                    instance="GetVariables",
                ),
                attribute_type=AttributeEnumType.actual,
            )
        ]
    )
    get_variables_result: GetVariableResultType = GetVariableResultType(
        **r.get_variable_result[0]
    )
    assert get_variables_result.attribute_status == GetVariableStatusEnumType.accepted
    items_per_message = json.loads(get_variables_result.attribute_value)
    log.debug(" max items per get variables request %d" % items_per_message)

    # request more than max items per message variables
    # get the value of ItemsPerMessage
    r: call_result201.GetVariables = await charge_point_v201.get_variables_req(
        get_variable_data=[
            GetVariableDataType(
                component=ComponentType(name="DeviceDataCtrlr"),
                variable=VariableType(
                    name="ItemsPerMessage",
                    instance="GetVariables",
                ),
                attribute_type=AttributeEnumType.actual,
            ),
            GetVariableDataType(
                component=ComponentType(name="TxCtrlr"),
                variable=VariableType(name="StopTxOnInvalidId"),
                attribute_type=AttributeEnumType.actual,
            ),
            GetVariableDataType(
                component=ComponentType(name="AlignedDataCtrlr"),
                variable=VariableType(name="Measurands"),
                attribute_type=AttributeEnumType.actual,
            ),
            GetVariableDataType(
                component=ComponentType(name="AuthCacheCtrlr"),
                variable=VariableType(name="Enabled"),
                attribute_type=AttributeEnumType.actual,
            ),
        ]
    )
    assert await wait_for_callerror_and_validate(
        test_utility, charge_point_v201, "OccurenceConstraintViolation"
    )


@pytest.mark.asyncio
@pytest.mark.ocpp_version("ocpp2.0.1")
async def test_B04(
    central_system_v201: CentralSystem,
    test_controller: TestController,
    test_utility: TestUtility,
):
    """
    B04.FR.01
    B04.FR.02
    ...
    """

    # prepare data for the test
    evse_id1 = 1
    connector_id = 1

    evse_id2 = 2

    @on(Action.boot_notification)
    def on_boot_notification_accepted(**kwargs):
        return call_result201.BootNotification(
            current_time=datetime.now(timezone.utc).isoformat(),
            interval=3,
            status=RegistrationStatusEnumType.accepted,
        )

    central_system_v201.function_overrides.append(
        ("on_boot_notification", on_boot_notification_accepted)
    )

    test_utility.validation_mode = ValidationMode.STRICT

    log.info(
        " ############################# Test case B04: Offline Idle Behaviour ###############################"
    )

    test_controller.start()
    charge_point_v201 = await central_system_v201.wait_for_chargepoint(
        wait_for_bootnotification=False
    )

    # setattr(charge_point_v201, 'on_boot_notification_accepted',on_boot_notification_accepted)
    central_system_v201.chargepoint.route_map = create_route_map(
        central_system_v201.chargepoint
    )

    # expect StatusNotification with status available
    assert await wait_for_and_validate(
        test_utility,
        charge_point_v201,
        "StatusNotification",
        call201.StatusNotification(
            datetime.now().isoformat(),
            ConnectorStatusEnumType.available,
            evse_id=evse_id1,
            connector_id=connector_id,
        ),
        validate_status_notification_201,
    )
    assert await wait_for_and_validate(
        test_utility,
        charge_point_v201,
        "StatusNotification",
        call201.StatusNotification(
            datetime.now().isoformat(),
            ConnectorStatusEnumType.available,
            evse_id=evse_id2,
            connector_id=connector_id,
        ),
        validate_status_notification_201,
    )

    # Set valid OfflineThreshold to 15s
    r: call_result201.SetVariables = (
        await charge_point_v201.set_config_variables_req(
            "OCPPCommCtrlr", "OfflineThreshold", "10"
        )
    )
    set_variable_result: SetVariableResultType = SetVariableResultType(
        **r.set_variable_result[0]
    )
    assert set_variable_result.attribute_status == SetVariableStatusEnumType.accepted

    test_utility.messages.clear()

    log.debug("========================B04.FR.01=========================")
    # Simulate connection loss
    test_controller.disconnect_websocket()

    # Wait 11 seconds
    await asyncio.sleep(11)

    # Connect CS
    log.debug(" Connect the CS to the CSMS")
    test_controller.connect_websocket()

    # wait for reconnect
    charge_point_v201 = await central_system_v201.wait_for_chargepoint(
        wait_for_bootnotification=False
    )

    # expect StatusNotification with status available as the disconnect duration was > than offline throeshold
    assert await wait_for_and_validate(
        test_utility,
        charge_point_v201,
        "StatusNotification",
        call201.StatusNotification(
            datetime.now().isoformat(),
            ConnectorStatusEnumType.available,
            evse_id=evse_id1,
            connector_id=connector_id,
        ),
        validate_status_notification_201,
    )

    assert await wait_for_and_validate(
        test_utility,
        charge_point_v201,
        "StatusNotification",
        call201.StatusNotification(
            datetime.now().isoformat(),
            ConnectorStatusEnumType.available,
            evse_id=evse_id2,
            connector_id=connector_id,
        ),
        validate_status_notification_201,
    )

    test_utility.messages.clear()

    # Wait 5 seconds
    await asyncio.sleep(5)

    log.debug("========================B04.FR.02=========================")

    # start charging session
    test_controller.plug_in(connector_id=2)

    # expect StatusNotification with status occupied
    assert await wait_for_and_validate(
        test_utility,
        charge_point_v201,
        "StatusNotification",
        call201.StatusNotification(
            datetime.now().isoformat(),
            ConnectorStatusEnumType.occupied,
            evse_id=evse_id2,
            connector_id=connector_id,
        ),
        validate_status_notification_201,
    )

    await asyncio.sleep(2)

    # Simulate connection loss
    test_controller.disconnect_websocket()

    # Wait 2 seconds
    await asyncio.sleep(2)

    # stop charging session
    test_controller.plug_out(connector_id=2)

    # Wait 3 seconds
    await asyncio.sleep(3)

    # Connect CS
    log.debug(" Connect the CS to the CSMS")
    test_controller.connect_websocket()

    # wait for reconnect
    charge_point_v201 = await central_system_v201.wait_for_chargepoint(
        wait_for_bootnotification=False
    )

    # expect StatusNotification with status available
    assert await wait_for_and_validate(
        test_utility,
        charge_point_v201,
        "StatusNotification",
        call201.StatusNotification(
            datetime.now().isoformat(),
            ConnectorStatusEnumType.available,
            evse_id=evse_id2,
            connector_id=connector_id,
        ),
        validate_status_notification_201,
    )

    test_utility.messages.clear()

    for _ in range(3):
        # send HeartBeat request when idle
        assert await wait_for_and_validate(
            test_utility, charge_point_v201, "Heartbeat", call.Heartbeat()
        )
