from unittest.mock import MagicMock, patch

from linode_api4.errors import ApiError

from prowler.providers.linode.services.compute.compute_service import (
    ComputeService,
)


def _mock_instance(
    id=1,
    label="my-instance",
    region="us-east",
    status="running",
    backups_enabled=True,
    disk_encryption="enabled",
    watchdog_enabled=True,
    tags=None,
):
    inst = MagicMock()
    inst.id = id
    inst.label = label
    region_mock = MagicMock()
    region_mock.id = region
    inst.region = region_mock
    inst.status = status
    backups = MagicMock()
    backups.enabled = backups_enabled
    inst.backups = backups
    inst.disk_encryption = disk_encryption
    inst.watchdog_enabled = watchdog_enabled
    inst.tags = tags or []
    return inst


def _build_service(linode_instances_return=None, linode_instances_side_effect=None):
    """Build a ComputeService with an isolated mock client."""
    service = object.__new__(ComputeService)
    service.instances = []

    # Build isolated mock hierarchy for client.linode.instances()
    # Must explicitly create the instances callable as a fresh MagicMock
    # because check tests contaminate MagicMock class with instances=[...]
    instances_callable = MagicMock()
    if linode_instances_side_effect:
        instances_callable.side_effect = linode_instances_side_effect
    else:
        instances_callable.return_value = linode_instances_return or []

    linode_mock = MagicMock()
    linode_mock.instances = instances_callable

    client_mock = MagicMock()
    client_mock.linode = linode_mock
    service.client = client_mock
    return service


class TestLinodeComputeService:
    def test_describe_instances_parses_correctly(self):
        mock_instances = [
            _mock_instance(id=1, label="web-1", region="us-east"),
            _mock_instance(id=2, label="db-1", region="eu-west", backups_enabled=False),
        ]

        service = _build_service(linode_instances_return=mock_instances)
        service._describe_instances()

        assert len(service.instances) == 2
        assert service.instances[0].label == "web-1"
        assert service.instances[0].region == "us-east"
        assert service.instances[0].backups_enabled is True
        assert service.instances[1].label == "db-1"
        assert service.instances[1].backups_enabled is False

    def test_describe_instances_handles_empty_list(self):
        service = _build_service(linode_instances_return=[])
        service._describe_instances()

        assert len(service.instances) == 0

    def test_describe_instances_handles_api_error(self):
        service = _build_service(linode_instances_side_effect=Exception("API error"))
        service._describe_instances()

        assert len(service.instances) == 0

    def test_describe_instances_missing_scope_logs_permission_error(self):
        error = ApiError(
            "Your OAuth token is not authorized to use this endpoint.", status=401
        )
        service = _build_service(linode_instances_side_effect=error)

        with patch(
            "prowler.providers.linode.lib.service.service.logger"
        ) as logger_mock:
            service._describe_instances()

        assert len(service.instances) == 0
        logged = " ".join(str(c) for c in logger_mock.error.call_args_list)
        assert "LinodeMissingPermissionError" in logged
        assert "linodes:read_only" in logged

    def test_describe_instances_disk_encryption(self):
        mock_instances = [
            _mock_instance(id=1, disk_encryption="enabled"),
            _mock_instance(id=2, disk_encryption="disabled"),
        ]

        service = _build_service(linode_instances_return=mock_instances)
        service._describe_instances()

        assert service.instances[0].disk_encryption == "enabled"
        assert service.instances[1].disk_encryption == "disabled"

    def test_describe_instances_region_filter_keeps_only_matching(self):
        mock_instances = [
            _mock_instance(id=1, label="eu", region="eu-central"),
            _mock_instance(id=2, label="us", region="us-east"),
            _mock_instance(id=3, label="eu-2", region="eu-central"),
        ]
        service = _build_service(linode_instances_return=mock_instances)
        service.provider = MagicMock()
        service.provider.regions = {"eu-central"}

        service._describe_instances()

        assert len(service.instances) == 2
        assert {i.label for i in service.instances} == {"eu", "eu-2"}
        assert all(i.region == "eu-central" for i in service.instances)

    def test_describe_instances_no_region_filter_keeps_all(self):
        mock_instances = [
            _mock_instance(id=1, region="eu-central"),
            _mock_instance(id=2, region="us-east"),
        ]
        service = _build_service(linode_instances_return=mock_instances)
        service.provider = MagicMock()
        service.provider.regions = None

        service._describe_instances()

        assert len(service.instances) == 2
