# Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import copy, grpc, logging, pytest
from common.tools.grpc.Tools import grpc_message_to_json_string
from context.client.ContextClient import ContextClient
from context.proto.context_pb2 import DeviceId
from device.client.DeviceClient import DeviceClient
from device.proto.context_pb2 import Device
from device.service.DeviceService import DeviceService
from device.service.driver_api._Driver import _Driver
from .PrepareTestScenario import ( # pylint: disable=unused-import
    # be careful, order of symbols is important here!
    mock_service, device_service, context_client, device_client, monitoring_client, test_prepare_environment)

from .mock_p4runtime_service import MockP4RuntimeService
try:
    from .device_p4 import(
        DEVICE_P4, DEVICE_P4_ID, DEVICE_P4_UUID, DEVICE_P4_ADDRESS, DEVICE_P4_PORT, DEVICE_P4_WORKERS,
        DEVICE_P4_GRACE_PERIOD, DEVICE_P4_CONNECT_RULES, DEVICE_P4_CONFIG_RULES)
    ENABLE_P4 = True
except ImportError:
    ENABLE_P4 = False

LOGGER = logging.getLogger(__name__)
LOGGER.setLevel(logging.DEBUG)

@pytest.fixture(scope='session')
def p4runtime_service():
    _service = MockP4RuntimeService(
        address=DEVICE_P4_ADDRESS, port=DEVICE_P4_PORT,
        max_workers=DEVICE_P4_WORKERS,
        grace_period=DEVICE_P4_GRACE_PERIOD)
    _service.start()
    yield _service
    _service.stop()


# ----- Test Device Driver P4 --------------------------------------------------

def test_device_p4_add_error_cases(
        context_client: ContextClient,   # pylint: disable=redefined-outer-name
        device_client: DeviceClient,     # pylint: disable=redefined-outer-name
        device_service: DeviceService):  # pylint: disable=redefined-outer-name

    if not ENABLE_P4: pytest.skip(
        'Skipping test: No P4 device has been configured')

    with pytest.raises(grpc.RpcError) as e:
        device_p4_with_extra_rules = copy.deepcopy(DEVICE_P4)
        device_p4_with_extra_rules['device_config']['config_rules'].extend(
            DEVICE_P4_CONNECT_RULES)
        device_p4_with_extra_rules['device_config']['config_rules'].extend(
            DEVICE_P4_CONFIG_RULES)
        device_client.AddDevice(Device(**device_p4_with_extra_rules))
    assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
    msg_head = 'device.device_config.config_rules(['
    msg_tail = ']) is invalid; RPC method AddDevice only accepts connection Config Rules that should start '\
               'with "_connect/" tag. Others should be configured after adding the device.'
    except_msg = str(e.value.details())
    assert except_msg.startswith(msg_head) and except_msg.endswith(msg_tail)


def test_device_p4_add_correct(
        context_client: ContextClient,              # pylint: disable=redefined-outer-name
        device_client: DeviceClient,                # pylint: disable=redefined-outer-name
        device_service: DeviceService,              # pylint: disable=redefined-outer-name
        p4runtime_service: MockP4RuntimeService):   # pylint: disable=redefined-outer-name

    if not ENABLE_P4: pytest.skip(
        'Skipping test: No P4 device has been configured')

    device_p4_with_connect_rules = copy.deepcopy(DEVICE_P4)
    device_p4_with_connect_rules['device_config']['config_rules'].extend(
        DEVICE_P4_CONNECT_RULES)
    device_client.AddDevice(Device(**device_p4_with_connect_rules))
    driver_instance_cache = device_service.device_servicer.driver_instance_cache
    driver : _Driver = driver_instance_cache.get(DEVICE_P4_UUID)
    assert driver is not None


def test_device_p4_get(
        context_client: ContextClient,              # pylint: disable=redefined-outer-name
        device_client: DeviceClient,                # pylint: disable=redefined-outer-name
        device_service: DeviceService,              # pylint: disable=redefined-outer-name
        p4runtime_service: MockP4RuntimeService):   # pylint: disable=redefined-outer-name

    if not ENABLE_P4: pytest.skip(
        'Skipping test: No P4 device has been configured')

    initial_config = device_client.GetInitialConfig(DeviceId(**DEVICE_P4_ID))
    LOGGER.info('initial_config = {:s}'.format(
        grpc_message_to_json_string(initial_config)))

    device_data = context_client.GetDevice(DeviceId(**DEVICE_P4_ID))
    LOGGER.info('device_data = {:s}'.format(
        grpc_message_to_json_string(device_data)))


def test_device_p4_configure(
        context_client: ContextClient,              # pylint: disable=redefined-outer-name
        device_client: DeviceClient,                # pylint: disable=redefined-outer-name
        device_service: DeviceService,              # pylint: disable=redefined-outer-name
        p4runtime_service: MockP4RuntimeService):   # pylint: disable=redefined-outer-name

    if not ENABLE_P4: pytest.skip(
        'Skipping test: No P4 device has been configured')

    pytest.skip('Skipping test for unimplemented method')


def test_device_p4_deconfigure(
        context_client: ContextClient,              # pylint: disable=redefined-outer-name
        device_client: DeviceClient,                # pylint: disable=redefined-outer-name
        device_service: DeviceService,              # pylint: disable=redefined-outer-name
        p4runtime_service: MockP4RuntimeService):   # pylint: disable=redefined-outer-name

    if not ENABLE_P4: pytest.skip(
        'Skipping test: No P4 device has been configured')

    pytest.skip('Skipping test for unimplemented method')


def test_device_p4_delete(
        context_client: ContextClient,              # pylint: disable=redefined-outer-name
        device_client: DeviceClient,                # pylint: disable=redefined-outer-name
        device_service: DeviceService,              # pylint: disable=redefined-outer-name
        p4runtime_service: MockP4RuntimeService):   # pylint: disable=redefined-outer-name

    if not ENABLE_P4: pytest.skip('Skipping test: No P4 device has been configured')

    device_client.DeleteDevice(DeviceId(**DEVICE_P4_ID))
    driver_instance_cache = device_service.device_servicer.driver_instance_cache
    driver : _Driver = driver_instance_cache.get(DEVICE_P4_UUID)
    assert driver is None
