import pytest
import pytest_mock
from temporalio.testing import ActivityEnvironment

from csirtmu.beta_temporal.config import Config as WorkerConfig
from csirtmu.beta_temporal.external_services.netbox.client import NetboxClient
from csirtmu.beta_temporal.external_services.netbox.structs import NetboxIPDTO, NetboxPrefixDTO, NetboxSubnetInfoDTO
from csirtmu.beta_temporal.external_services.rt import structs
from csirtmu.beta_temporal.external_services.rt.client import RTClient
from csirtmu.beta_temporal.workflows.close_automatic_tickets_from_outside import (
    CloseAutomaticTicketsFromOutsideActivities,
)


class TestIsAutomaticCreateFromOutside:
    @staticmethod
    def prepare_mocks(
        mock_ticket_data: structs.TicketData,
        mock_subnet_info: NetboxSubnetInfoDTO,
        mocker: pytest_mock.MockerFixture,
        headers: dict[str, str],
        prefixes: list[NetboxPrefixDTO] | None = None,
        ips: list[NetboxIPDTO] | None = None,
    ) -> tuple[pytest_mock.MockType, pytest_mock.MockType]:
        for k, v in headers.items():
            mock_ticket_data.headers.add_header(k, v)
        if "From" not in headers:
            mock_ticket_data.headers.add_header("From", "ToolGateway")
        if "X-RT-Incident-IP" not in headers:
            mock_ticket_data.headers.add_header("X-RT-Incident-IP", "1.1.1.1")
        if "X-RT-Incident-Actors" not in headers:
            mock_ticket_data.headers.add_header("X-RT-Incident-Actors", "IDK")
        if "X-RT-Manual-Handling" not in headers:
            mock_ticket_data.headers.add_header("X-RT-Manual-Handling", "0")

        get_ticket_data_mock = mocker.patch(
            "csirtmu.beta_temporal.workflows.close_automatic_tickets_from_outside.RTClient.get_ticket_data"
        )
        get_ticket_data_mock.return_value = mock_ticket_data

        mock_subnet_info.prefixes = prefixes or []
        mock_subnet_info.ip_addresses = ips or []

        subnet_info_mock = mocker.patch(
            "csirtmu.beta_temporal.workflows.close_automatic_tickets_from_outside.NetboxClient.get_subnet_info_on_ip"
        )
        subnet_info_mock.return_value = mock_subnet_info
        return get_ticket_data_mock, subnet_info_mock

    @staticmethod
    def assert_mocks_called_correctly(
        get_ticket_data_mock: pytest_mock.MockType,
        subnet_info_mock: pytest_mock.MockType,
        ticket_data: structs.TransactionStruct | None = None,
        subnet_info: str | None = None,
    ) -> None:
        if ticket_data is None:
            get_ticket_data_mock.assert_not_called()
        else:
            get_ticket_data_mock.assert_called_once_with(ticket_data)
        if subnet_info is None:
            subnet_info_mock.assert_not_called()
        else:
            subnet_info_mock.assert_called_once_with(subnet_info)

    @pytest.mark.parametrize("status", ["act", "new"])
    async def test_is_automatic_create_from_outside_true_new(
        self,
        status: str,
        worker_config: WorkerConfig,
        test_activity_env: ActivityEnvironment,
        mock_transaction_obj: structs.TransactionStruct,
        mock_ticket_data: structs.TicketData,
        mock_subnet_info: NetboxSubnetInfoDTO,
        mocker: pytest_mock.MockerFixture,
    ) -> None:
        assert mock_transaction_obj.ticket_obj is not None
        mock_transaction_obj.ticket_obj.status = status

        get_ticket_data_mock, subnet_info_mock = self.prepare_mocks(mock_ticket_data, mock_subnet_info, mocker, {})

        activities = CloseAutomaticTicketsFromOutsideActivities(
            RTClient(worker_config.rt), NetboxClient(worker_config.netbox)
        )
        assert await test_activity_env.run(activities.is_automatic_create_from_outside, mock_transaction_obj)

        self.assert_mocks_called_correctly(get_ticket_data_mock, subnet_info_mock, mock_transaction_obj, "1.1.1.1")

    async def test_is_automatic_create_from_outside_not_from_tool_gateway(
        self,
        worker_config: WorkerConfig,
        test_activity_env: ActivityEnvironment,
        mock_transaction_obj: structs.TransactionStruct,
        mock_ticket_data: structs.TicketData,
        mock_subnet_info: NetboxSubnetInfoDTO,
        mocker: pytest_mock.MockerFixture,
    ) -> None:
        assert mock_transaction_obj.ticket_obj is not None
        mock_transaction_obj.ticket_obj.status = "act"

        get_ticket_data_mock, subnet_info_mock = self.prepare_mocks(
            mock_ticket_data, mock_subnet_info, mocker, {"From": "IDK"}
        )

        activities = CloseAutomaticTicketsFromOutsideActivities(
            RTClient(worker_config.rt), NetboxClient(worker_config.netbox)
        )
        assert not await test_activity_env.run(activities.is_automatic_create_from_outside, mock_transaction_obj)

        self.assert_mocks_called_correctly(get_ticket_data_mock, subnet_info_mock, mock_transaction_obj, "1.1.1.1")

    async def test_is_automatic_create_from_outside_wrong_status(
        self,
        worker_config: WorkerConfig,
        test_activity_env: ActivityEnvironment,
        mock_transaction_obj: structs.TransactionStruct,
        mock_ticket_data: structs.TicketData,
        mock_subnet_info: NetboxSubnetInfoDTO,
        mocker: pytest_mock.MockerFixture,
    ) -> None:
        assert mock_transaction_obj.ticket_obj is not None
        mock_transaction_obj.ticket_obj.status = "resolved"

        get_ticket_data_mock, subnet_info_mock = self.prepare_mocks(mock_ticket_data, mock_subnet_info, mocker, {})

        activities = CloseAutomaticTicketsFromOutsideActivities(
            RTClient(worker_config.rt), NetboxClient(worker_config.netbox)
        )
        assert not await test_activity_env.run(activities.is_automatic_create_from_outside, mock_transaction_obj)

        self.assert_mocks_called_correctly(get_ticket_data_mock, subnet_info_mock, mock_transaction_obj, "1.1.1.1")

    async def test_is_automatic_create_from_outside_friendly_address(
        self,
        worker_config: WorkerConfig,
        test_activity_env: ActivityEnvironment,
        mock_transaction_obj: structs.TransactionStruct,
        mock_ticket_data: structs.TicketData,
        mock_subnet_info: NetboxSubnetInfoDTO,
        mocker: pytest_mock.MockerFixture,
    ) -> None:
        assert mock_transaction_obj.ticket_obj is not None
        mock_transaction_obj.ticket_obj.status = "act"

        get_ticket_data_mock, subnet_info_mock = self.prepare_mocks(
            mock_ticket_data, mock_subnet_info, mocker, {"X-RT-Incident-IP": "195.113.0.37/32"}
        )

        activities = CloseAutomaticTicketsFromOutsideActivities(
            RTClient(worker_config.rt), NetboxClient(worker_config.netbox)
        )
        assert not await test_activity_env.run(activities.is_automatic_create_from_outside, mock_transaction_obj)

        self.assert_mocks_called_correctly(
            get_ticket_data_mock, subnet_info_mock, mock_transaction_obj, "195.113.0.37/32"
        )

    async def test_is_automatic_create_from_outside_prefix_in_netbox(
        self,
        worker_config: WorkerConfig,
        test_activity_env: ActivityEnvironment,
        mock_transaction_obj: structs.TransactionStruct,
        mock_ticket_data: structs.TicketData,
        mock_subnet_info: NetboxSubnetInfoDTO,
        mocker: pytest_mock.MockerFixture,
    ) -> None:
        assert mock_transaction_obj.ticket_obj is not None
        mock_transaction_obj.ticket_obj.status = "act"

        get_ticket_data_mock, subnet_info_mock = self.prepare_mocks(
            mock_ticket_data, mock_subnet_info, mocker, {}, mock_subnet_info.prefixes
        )

        activities = CloseAutomaticTicketsFromOutsideActivities(
            RTClient(worker_config.rt), NetboxClient(worker_config.netbox)
        )
        assert not await test_activity_env.run(activities.is_automatic_create_from_outside, mock_transaction_obj)

        self.assert_mocks_called_correctly(get_ticket_data_mock, subnet_info_mock, mock_transaction_obj, "1.1.1.1")

    async def test_is_automatic_create_from_outside_ip_in_netbox(
        self,
        worker_config: WorkerConfig,
        test_activity_env: ActivityEnvironment,
        mock_transaction_obj: structs.TransactionStruct,
        mock_ticket_data: structs.TicketData,
        mock_subnet_info: NetboxSubnetInfoDTO,
        mocker: pytest_mock.MockerFixture,
    ) -> None:
        assert mock_transaction_obj.ticket_obj is not None
        mock_transaction_obj.ticket_obj.status = "act"

        get_ticket_data_mock, subnet_info_mock = self.prepare_mocks(
            mock_ticket_data, mock_subnet_info, mocker, {}, ips=mock_subnet_info.ip_addresses
        )

        activities = CloseAutomaticTicketsFromOutsideActivities(
            RTClient(worker_config.rt), NetboxClient(worker_config.netbox)
        )
        assert not await test_activity_env.run(activities.is_automatic_create_from_outside, mock_transaction_obj)

        self.assert_mocks_called_correctly(get_ticket_data_mock, subnet_info_mock, mock_transaction_obj, "1.1.1.1")

    async def test_is_automatic_create_from_outside_requires_manual_handling(
        self,
        worker_config: WorkerConfig,
        test_activity_env: ActivityEnvironment,
        mock_transaction_obj: structs.TransactionStruct,
        mock_ticket_data: structs.TicketData,
        mock_subnet_info: NetboxSubnetInfoDTO,
        mocker: pytest_mock.MockerFixture,
    ) -> None:
        assert mock_transaction_obj.ticket_obj is not None
        mock_transaction_obj.ticket_obj.status = "act"

        get_ticket_data_mock, subnet_info_mock = self.prepare_mocks(
            mock_ticket_data, mock_subnet_info, mocker, {"X-RT-Manual-Handling": "1"}
        )

        activities = CloseAutomaticTicketsFromOutsideActivities(
            RTClient(worker_config.rt), NetboxClient(worker_config.netbox)
        )
        assert not await test_activity_env.run(activities.is_automatic_create_from_outside, mock_transaction_obj)

        self.assert_mocks_called_correctly(
            get_ticket_data_mock,
            subnet_info_mock,
            mock_transaction_obj,
        )

    async def test_is_automatic_create_from_outside_uco_in_actors(
        self,
        worker_config: WorkerConfig,
        test_activity_env: ActivityEnvironment,
        mock_transaction_obj: structs.TransactionStruct,
        mock_ticket_data: structs.TicketData,
        mock_subnet_info: NetboxSubnetInfoDTO,
        mocker: pytest_mock.MockerFixture,
    ) -> None:
        assert mock_transaction_obj.ticket_obj is not None
        mock_transaction_obj.ticket_obj.status = "act"

        get_ticket_data_mock, subnet_info_mock = self.prepare_mocks(
            mock_ticket_data, mock_subnet_info, mocker, {"X-RT-Incident-Actors": "UCO=123"}
        )

        activities = CloseAutomaticTicketsFromOutsideActivities(
            RTClient(worker_config.rt), NetboxClient(worker_config.netbox)
        )
        assert not await test_activity_env.run(activities.is_automatic_create_from_outside, mock_transaction_obj)

        self.assert_mocks_called_correctly(
            get_ticket_data_mock,
            subnet_info_mock,
            mock_transaction_obj,
        )

    async def test_is_automatic_create_from_outside_ip_in_actors(
        self,
        worker_config: WorkerConfig,
        test_activity_env: ActivityEnvironment,
        mock_transaction_obj: structs.TransactionStruct,
        mock_ticket_data: structs.TicketData,
        mock_subnet_info: NetboxSubnetInfoDTO,
        mocker: pytest_mock.MockerFixture,
    ) -> None:
        assert mock_transaction_obj.ticket_obj is not None
        mock_transaction_obj.ticket_obj.status = "act"

        get_ticket_data_mock, subnet_info_mock = self.prepare_mocks(
            mock_ticket_data, mock_subnet_info, mocker, {"X-RT-Incident-Actors": "147.251.0.0"}
        )

        activities = CloseAutomaticTicketsFromOutsideActivities(
            RTClient(worker_config.rt), NetboxClient(worker_config.netbox)
        )
        assert not await test_activity_env.run(activities.is_automatic_create_from_outside, mock_transaction_obj)

        self.assert_mocks_called_correctly(
            get_ticket_data_mock,
            subnet_info_mock,
            mock_transaction_obj,
        )

    async def test_is_automatic_create_from_outside_incident_ip(
        self,
        worker_config: WorkerConfig,
        test_activity_env: ActivityEnvironment,
        mock_transaction_obj: structs.TransactionStruct,
        mock_ticket_data: structs.TicketData,
        mock_subnet_info: NetboxSubnetInfoDTO,
        mocker: pytest_mock.MockerFixture,
    ) -> None:
        assert mock_transaction_obj.ticket_obj is not None
        mock_transaction_obj.ticket_obj.status = "act"

        get_ticket_data_mock, subnet_info_mock = self.prepare_mocks(
            mock_ticket_data, mock_subnet_info, mocker, {"X-RT-Incident-IP": "147.251.0.0"}
        )

        activities = CloseAutomaticTicketsFromOutsideActivities(
            RTClient(worker_config.rt), NetboxClient(worker_config.netbox)
        )
        assert not await test_activity_env.run(activities.is_automatic_create_from_outside, mock_transaction_obj)

        self.assert_mocks_called_correctly(
            get_ticket_data_mock,
            subnet_info_mock,
            mock_transaction_obj,
        )
