"""
Unit tests for testing the netbox client
"""

import uuid
from os import PathLike
from unittest.mock import Mock, patch

import pytest
from pynetbox.core.response import Record

from gso.products.product_blocks.site import SiteTier
from gso.services.netbox_client import NetboxClient
from gso.utils.exceptions import WorkflowStateError

BASE_URL = "https://127.0.0.1:8000"


@pytest.fixture(scope="module")
def device():
    values = {"id": 1, "name": "test123"}
    return Record(values, None, None)


@pytest.fixture(scope="module")
def device_type():
    values = {"id": 1, "name": "test123"}
    return Record(values, None, None)


@pytest.fixture(scope="module")
def device_role():
    values = {"id": 1, "name": "test123"}
    return Record(values, None, None)


@pytest.fixture(scope="module")
def site():
    values = {"id": 1, "name": "test123"}
    return Record(values, None, None)


@pytest.fixture(scope="module")
def device_bay():
    values = {"id": 1, "name": "bay_test", "position": 1}
    return Record(values, None, None)


@pytest.fixture(scope="module")
def card_type():
    values = {"id": 1, "name": "test_card_type"}
    return Record(values, None, None)


@pytest.fixture(scope="module")
def interface():
    values = {
        "id": 1,
        "name": "et-0/0/1",
        "speed": 1000,
        "type": "1000BaseT",
        "enabled": False,
        "mark_connected": False,
    }
    return Record(values, None, None)


@pytest.fixture(scope="module")
def lag():
    values = {
        "id": 1,
        "name": "lag-1",
        "type": "lag",
    }
    return Record(values, None, None)


@patch("gso.services.netbox_client.pynetbox.api")
def test_create_device(
    mock_api, device, device_type, device_role, site, device_bay, card_type, data_config_filename: PathLike
):
    device_name = "mx1.lab.geant.net"
    device.name = device_name
    site_tier = SiteTier.TIER1

    # Define mock calls
    mock_api.return_value.dcim.device_types.get.return_value = device_type
    mock_api.return_value.dcim.device_roles.get.return_value = device_role
    mock_api.return_value.dcim.sites.get.return_value = site
    mock_api.return_value.dcim.devices.create.return_value = device
    mock_api.return_value.dcim.module_bays.filter.return_value = [device_bay]
    mock_api.return_value.dcim.module_types.get.return_value = card_type
    mock_api.return_value.dcim.module_types.create.return_value = card_type

    new_device = NetboxClient().create_device(device_name, site_tier)
    assert new_device is not None
    assert new_device.name == device_name


@patch("gso.services.netbox_client.Router.from_subscription")
@patch("gso.services.netbox_client.pynetbox.api")
def test_get_available_lags(mock_api, mock_from_subscription, data_config_filename: PathLike):
    router_id = uuid.uuid4()
    feasible_lags = [f"LAG-{i}" for i in range(1, 11)]

    # Mock the pynetbox API instance
    mock_netbox = mock_api.return_value
    mock_filter = mock_netbox.dcim.interfaces.filter
    mock_filter.return_value = [{"name": f"LAG-{i}", "type": "lag"} for i in range(1, 4)]

    # Mock the Router.from_subscription method
    mock_subscription = mock_from_subscription.return_value
    mock_router = mock_subscription.router
    mock_router.router_fqdn = "test_router"

    netbox_client = NetboxClient()
    result = netbox_client.get_available_lags(router_id)

    # Check the result of the function
    assert result == [lag for lag in feasible_lags if lag not in [f"LAG-{i}" for i in range(1, 4)]]


@patch("gso.services.netbox_client.pynetbox.api")
def test_create_interface(mock_api, device, interface, data_config_filename: PathLike):
    # Moch netbox calls
    mock_api.return_value.dcim.devices.get.return_value = device
    mock_api.return_value.dcim.interfaces.create.return_value = interface

    # Create new interface
    new_interface = NetboxClient().create_interface(interface.name, interface.type, interface.speed, device.name)

    # Check result
    assert new_interface is not None
    assert new_interface.name == interface.name


@patch("gso.services.netbox_client.pynetbox.api")
def test_reserve_interface_exception(mock_api, device, interface, data_config_filename: PathLike):
    """
    If the interface is already reserved
    the method should throw an exception
    """
    # Change the interface to reserved
    interface.enabled = True

    # expected exception message
    exception_message = f"The interface: {interface.name} on device: {device.name} is already reserved."

    # Mock netbox api
    mock_api.return_value.dcim.devices.get.return_value = device
    mock_api.return_value.dcim.interfaces.get.return_value = interface

    # Check exception
    with pytest.raises(WorkflowStateError) as test_exception:
        NetboxClient().reserve_interface(device.name, interface.name)
        assert str(test_exception.value) == exception_message


@patch("gso.services.netbox_client.pynetbox.api")
def test_reserve_interface(mock_api, device, interface, data_config_filename: PathLike):
    """
    Test a normal reservation of a interface
    """
    # Set interface to not reserved
    interface.enabled = False

    # Mock netbox api
    mock_api.return_value.dcim.devices.get.return_value = device
    mock_api.return_value.dcim.interfaces.get.return_value = interface

    # mock save method
    mock_save = Mock()
    mock_save.save.return_value = interface
    interface.save = mock_save

    # Check reservation of interface
    updated_interface = NetboxClient().reserve_interface(device.name, interface.name)

    assert updated_interface is not None
    assert updated_interface.enabled is True
    mock_save.assert_called_once()


@patch("gso.services.netbox_client.pynetbox.api")
def test_allocate_interface_exception(mock_api, device, interface, data_config_filename: PathLike):
    """
    If the interface is already allocated
    the method should throw an exception
    """
    # Change the interface to reserved
    interface.enabled = True

    # Change interface to allocated
    interface.mark_connected = True

    # expected exception message
    exception_message = f"The interface: {interface.name} on device: {device.name} is already allocated."

    # Mock netbox api
    mock_api.return_value.dcim.devices.get.return_value = device
    mock_api.return_value.dcim.interfaces.get.return_value = interface

    # Check exception
    with pytest.raises(WorkflowStateError) as test_exception:
        NetboxClient().allocate_interface(device.name, interface.name)
        assert str(test_exception.value) == exception_message


@patch("gso.services.netbox_client.pynetbox.api")
def test_allocation_interface(mock_api, device, interface, data_config_filename: PathLike):
    """
    Test a normal allocation of a interface
    """
    # Set interface to not allocated
    interface.mark_connected = False

    # Mock netbox api
    mock_api.return_value.dcim.devices.get.return_value = device
    mock_api.return_value.dcim.interfaces.get.return_value = interface

    # mock save method
    mock_save = Mock()
    mock_save.save.return_value = interface
    interface.save = mock_save

    # Check allocation of interface
    updated_interface = NetboxClient().allocate_interface(device.name, interface.name)

    assert updated_interface is not None
    assert updated_interface.mark_connected is True
    mock_save.assert_called_once()


@patch("gso.services.netbox_client.pynetbox.api")
def test_delete_device(mock_api, device, data_config_filename: PathLike):
    """
    Test a delete of a device
    """
    # Mock netbox api
    mock_api.return_value.dcim.devices.get.return_value = device

    # mock delete method
    mock_delete = Mock()
    device.delete = mock_delete

    # Check delete of interface
    NetboxClient().delete_device(device.name)

    mock_delete.assert_called_once()


@patch("gso.services.netbox_client.pynetbox.api")
def test_get_interfaces_by_device(mock_api, device, interface, data_config_filename: PathLike):
    """
    Test if a interface is returned for a device
    """
    # Setup interface speed
    speed = 1000

    # Mock netbox api
    mock_api.return_value.dcim.devices.get.return_value = device
    mock_api.return_value.dcim.interfaces.filter.return_value = [interface, interface]

    # Call get interfaces by device
    interfaces = NetboxClient().get_interfaces_by_device(device.name, speed)

    assert interfaces is not None
    assert len(interfaces) == 2


@patch("gso.services.netbox_client.pynetbox.api")
def test_attach_interface_to_lag(mock_api, device, interface, lag, data_config_filename: PathLike):
    """
    Test if a interface is attached correctly to a lag interface
    """

    # Define site effect function
    def get_side_effect(**kwargs):
        if kwargs.get("device_id") == 1 and kwargs.get("name") == "lag-1":
            return lag
        return interface

    # Define a description
    description = "test123"

    # Mock netbox api
    mock_api.return_value.dcim.devices.get.return_value = device
    mock_api.return_value.dcim.interfaces.get.side_effect = get_side_effect

    # mock save method
    mock_save = Mock()
    mock_save.save.return_value = interface
    interface.save = mock_save

    # Check if interface attached to lag
    lag_interface = NetboxClient().attach_interface_to_lag(device.name, lag.name, interface.name, description)

    assert lag_interface is not None
    assert lag_interface.lag == lag.id
    assert lag_interface.description == description
    mock_save.assert_called_once()