from typing import Any

import pytest_mock
from gitlab import Gitlab
from temporalio.testing import ActivityEnvironment

from csirtmu.beta_temporal.config import Config as WorkerConfig
from csirtmu.beta_temporal.external_services.gitlab.client import GitlabClient
from csirtmu.beta_temporal.external_services.rt import structs
from csirtmu.beta_temporal.external_services.rt.client import RTClient
from csirtmu.beta_temporal.workflows.create_gitlab_issue_on_certificate_ticket import (
    CreateGitlabIssueOnCertificateTicketActivities,
)


class TestCreateOrUpdateGitlabIssue:
    @staticmethod
    def prepare_mocks(
        mocker: pytest_mock.MockerFixture,
        gitlab_issues: list[Any],
        mock_ticket_data: structs.TicketData | None = None,
    ) -> tuple[pytest_mock.MockType, pytest_mock.MockType, pytest_mock.MockType, pytest_mock.MockType]:
        gitlab_get_issues_mock = mocker.patch(
            "csirtmu.beta_temporal.workflows.create_gitlab_issue_on_certificate_ticket.GitlabClient.get_open_issues",
        )
        gitlab_get_issues_mock.return_value = gitlab_issues
        get_ticket_data_mock = mocker.patch(
            "csirtmu.beta_temporal.workflows.create_gitlab_issue_on_certificate_ticket.RTClient.get_ticket_data",
        )
        get_ticket_data_mock.return_value = mock_ticket_data
        gitlab_comment_issue_mock = mocker.patch(
            "csirtmu.beta_temporal.workflows.create_gitlab_issue_on_certificate_ticket.GitlabClient.comment_issue",
        )
        gitlab_comment_issue_mock.return_value = None
        gitlab_create_issue_mock = mocker.patch(
            "csirtmu.beta_temporal.workflows.create_gitlab_issue_on_certificate_ticket.GitlabClient.create_issue",
        )
        gitlab_create_issue_mock.return_value = -1
        return gitlab_get_issues_mock, get_ticket_data_mock, gitlab_comment_issue_mock, gitlab_create_issue_mock

    @staticmethod
    def assert_mock_calls(
        gitlab_get_issues_mock: pytest_mock.MockType | None,
        get_ticket_data_mock: pytest_mock.MockType,
        gitlab_comment_issue_mock: pytest_mock.MockType,
        gitlab_create_issue_mock: pytest_mock.MockType,
        get_ticket_data_mock_args: structs.TransactionStruct | None = None,
        gitlab_comment_issue_args: tuple[int, str] | None = None,
        gitlab_create_issue_args: dict[str, str] | None = None,
    ) -> None:
        if gitlab_get_issues_mock is not None:
            gitlab_get_issues_mock.assert_called_once()
        if get_ticket_data_mock_args is not None:
            get_ticket_data_mock.assert_called_once_with(get_ticket_data_mock_args)
        else:
            get_ticket_data_mock.assert_not_called()
        if gitlab_comment_issue_args is not None:
            gitlab_comment_issue_mock.assert_called_once_with(*gitlab_comment_issue_args)
        else:
            gitlab_comment_issue_mock.assert_not_called()
        if gitlab_create_issue_args is not None:
            gitlab_create_issue_mock.assert_called_once_with(**gitlab_create_issue_args)
        else:
            gitlab_create_issue_mock.assert_not_called()

    async def test_create_issue_on_certificate_expiration_notice(
        self,
        worker_config: WorkerConfig,
        test_activity_env: ActivityEnvironment,
        mock_transaction_obj: structs.TransactionStruct,
        mocker: pytest_mock.MockerFixture,
    ) -> None:
        assert mock_transaction_obj.ticket_obj is not None
        cn = "test.csirt.muni.cz"
        mock_transaction_obj.ticket_obj.subject = f"Expirace certifikátu {cn} (TCS3)"
        gitlab_get_issues_mock, get_ticket_data_mock, gitlab_comment_issue_mock, gitlab_create_issue_mock = (
            self.prepare_mocks(
                mocker,
                [],
            )
        )
        activities = CreateGitlabIssueOnCertificateTicketActivities(
            GitlabClient(Gitlab(), worker_config.gitlab), RTClient(worker_config.rt), worker_config.rt.base_url
        )
        await test_activity_env.run(activities.create_or_update_gitlab_issue, mock_transaction_obj)

        self.assert_mock_calls(
            gitlab_get_issues_mock,
            get_ticket_data_mock,
            gitlab_comment_issue_mock,
            gitlab_create_issue_mock,
            gitlab_create_issue_args={
                "title": f"Expiration of certificate {cn}",
                "description": f"Expiration notification received for CN `{cn}` [Ticket]({worker_config.rt.base_url}Ticket/Display.html?id={mock_transaction_obj.ticket_obj.id})",
            },
        )

    async def test_comment_issue_on_certificate_expiration_notice(
        self,
        worker_config: WorkerConfig,
        test_activity_env: ActivityEnvironment,
        mock_transaction_obj: structs.TransactionStruct,
        mock_gitlab_issue: type[Any],
        mocker: pytest_mock.MockerFixture,
    ) -> None:
        assert mock_transaction_obj.ticket_obj is not None
        cn = "test.csirt.muni.cz"
        mock_transaction_obj.ticket_obj.subject = f"Expirace certifikátu {cn} (TCS3)"

        gitlab_get_issues_mock, get_ticket_data_mock, gitlab_comment_issue_mock, gitlab_create_issue_mock = (
            self.prepare_mocks(
                mocker,
                [mock_gitlab_issue(f"Expiration of certificate {cn}", 11)],
            )
        )
        activities = CreateGitlabIssueOnCertificateTicketActivities(
            GitlabClient(Gitlab(), worker_config.gitlab), RTClient(worker_config.rt), worker_config.rt.base_url
        )
        await test_activity_env.run(activities.create_or_update_gitlab_issue, mock_transaction_obj)

        self.assert_mock_calls(
            gitlab_get_issues_mock,
            get_ticket_data_mock,
            gitlab_comment_issue_mock,
            gitlab_create_issue_mock,
            gitlab_comment_issue_args=(
                11,
                f"New expiration notification received: [Ticket]({worker_config.rt.base_url}Ticket/Display.html?id={mock_transaction_obj.ticket_obj.id})",
            ),
        )

    async def test_comment_issue_on_certificate_request_confirmation_notice(
        self,
        worker_config: WorkerConfig,
        test_activity_env: ActivityEnvironment,
        mock_ticket_data: structs.TicketData,
        mock_transaction_obj: structs.TransactionStruct,
        mock_gitlab_issue: type[Any],
        mocker: pytest_mock.MockerFixture,
    ) -> None:
        assert mock_transaction_obj.ticket_obj is not None
        cn = "test.csirt.muni.cz"
        mock_transaction_obj.ticket_obj.subject = "Confirmation of TCS certificate request submission"
        mock_ticket_data.body = f"dummy\ndummy\ndummy\n\nSUBJECT:XDXDXDXDCN={cn},O=XDXDXDXD\n\n\ndummy\n\n\n"

        gitlab_get_issues_mock, get_ticket_data_mock, gitlab_comment_issue_mock, gitlab_create_issue_mock = (
            self.prepare_mocks(
                mocker,
                [mock_gitlab_issue(f"Expiration of certificate {cn}", 11)],
                mock_ticket_data,
            )
        )
        activities = CreateGitlabIssueOnCertificateTicketActivities(
            GitlabClient(Gitlab(), worker_config.gitlab), RTClient(worker_config.rt), worker_config.rt.base_url
        )
        await test_activity_env.run(activities.create_or_update_gitlab_issue, mock_transaction_obj)

        self.assert_mock_calls(
            gitlab_get_issues_mock,
            get_ticket_data_mock,
            gitlab_comment_issue_mock,
            gitlab_create_issue_mock,
            get_ticket_data_mock_args=mock_transaction_obj,
            gitlab_comment_issue_args=(
                11,
                f"Received confirmation for certificate request for `{cn}`: [Ticket]({worker_config.rt.base_url}Ticket/Display.html?id={mock_transaction_obj.ticket_obj.id})",
            ),
        )

    async def test_comment_issue_on_certificate_request_confirmation_notice_in_cz(
        self,
        worker_config: WorkerConfig,
        test_activity_env: ActivityEnvironment,
        mock_ticket_data: structs.TicketData,
        mock_transaction_obj: structs.TransactionStruct,
        mock_gitlab_issue: type[Any],
        mocker: pytest_mock.MockerFixture,
    ) -> None:
        assert mock_transaction_obj.ticket_obj is not None
        cn = "test.csirt.muni.cz"
        mock_transaction_obj.ticket_obj.subject = (
            "Potvrzení podání žádosti o certifikát TCS #1111111111 (test@test.org)"
        )
        mock_ticket_data.body = f"dummy\ndummy\ndummy\n\nSUBJECT:XDXDXDXDCN={cn},O=XDXDXDXD\n\n\ndummy\n\n\n"

        gitlab_get_issues_mock, get_ticket_data_mock, gitlab_comment_issue_mock, gitlab_create_issue_mock = (
            self.prepare_mocks(
                mocker,
                [mock_gitlab_issue(f"Expiration of certificate {cn}", 11)],
                mock_ticket_data,
            )
        )
        activities = CreateGitlabIssueOnCertificateTicketActivities(
            GitlabClient(Gitlab(), worker_config.gitlab), RTClient(worker_config.rt), worker_config.rt.base_url
        )
        await test_activity_env.run(activities.create_or_update_gitlab_issue, mock_transaction_obj)

        self.assert_mock_calls(
            gitlab_get_issues_mock,
            get_ticket_data_mock,
            gitlab_comment_issue_mock,
            gitlab_create_issue_mock,
            get_ticket_data_mock_args=mock_transaction_obj,
            gitlab_comment_issue_args=(
                11,
                f"Received confirmation for certificate request for `{cn}`: [Ticket]({worker_config.rt.base_url}Ticket/Display.html?id={mock_transaction_obj.ticket_obj.id})",
            ),
        )

    async def test_comment_issue_on_certificate_request_approval_notice(
        self,
        worker_config: WorkerConfig,
        test_activity_env: ActivityEnvironment,
        mock_transaction_obj: structs.TransactionStruct,
        mock_gitlab_issue: type[Any],
        mocker: pytest_mock.MockerFixture,
    ) -> None:
        assert mock_transaction_obj.ticket_obj is not None
        cn = "test.csirt.muni.cz"
        mock_transaction_obj.ticket_obj.subject = (
            f"Schválení žádosti o certifikát TCS / A TCS Certificate Request Has Been Accepted #1719398396 ({cn})"
        )

        gitlab_get_issues_mock, get_ticket_data_mock, gitlab_comment_issue_mock, gitlab_create_issue_mock = (
            self.prepare_mocks(
                mocker,
                [mock_gitlab_issue(f"Expiration of certificate {cn}", 11)],
            )
        )
        activities = CreateGitlabIssueOnCertificateTicketActivities(
            GitlabClient(Gitlab(), worker_config.gitlab), RTClient(worker_config.rt), worker_config.rt.base_url
        )
        await test_activity_env.run(activities.create_or_update_gitlab_issue, mock_transaction_obj)

        self.assert_mock_calls(
            gitlab_get_issues_mock,
            get_ticket_data_mock,
            gitlab_comment_issue_mock,
            gitlab_create_issue_mock,
            gitlab_comment_issue_args=(
                11,
                f"Received certificate request approval: [Ticket]({worker_config.rt.base_url}Ticket/Display.html?id={mock_transaction_obj.ticket_obj.id})",
            ),
        )

    async def test_comment_issue_on_certificate_release_notice(
        self,
        worker_config: WorkerConfig,
        test_activity_env: ActivityEnvironment,
        mock_transaction_obj: structs.TransactionStruct,
        mock_gitlab_issue: type[Any],
        mocker: pytest_mock.MockerFixture,
    ) -> None:
        assert mock_transaction_obj.ticket_obj is not None
        cn = "test.csirt.muni.cz"
        mock_transaction_obj.ticket_obj.subject = (
            f"Oznámení o vydání certifikátu TCS / A TCS Certificate Has Been Issued #1718972734 ({cn})"
        )

        gitlab_get_issues_mock, get_ticket_data_mock, gitlab_comment_issue_mock, gitlab_create_issue_mock = (
            self.prepare_mocks(
                mocker,
                [mock_gitlab_issue(f"Expiration of certificate {cn}", 11)],
            )
        )
        activities = CreateGitlabIssueOnCertificateTicketActivities(
            GitlabClient(Gitlab(), worker_config.gitlab), RTClient(worker_config.rt), worker_config.rt.base_url
        )
        await test_activity_env.run(activities.create_or_update_gitlab_issue, mock_transaction_obj)

        self.assert_mock_calls(
            gitlab_get_issues_mock,
            get_ticket_data_mock,
            gitlab_comment_issue_mock,
            gitlab_create_issue_mock,
            gitlab_comment_issue_args=(
                11,
                f"New certificate was released: [Ticket]({worker_config.rt.base_url}Ticket/Display.html?id={mock_transaction_obj.ticket_obj.id})",
            ),
        )

    async def test_notify_on_unknown_ticket_format(
        self,
        worker_config: WorkerConfig,
        test_activity_env: ActivityEnvironment,
        mock_transaction_obj: structs.TransactionStruct,
        mocker: pytest_mock.MockerFixture,
    ) -> None:
        assert mock_transaction_obj.ticket_obj is not None
        mock_transaction_obj.ticket_obj.subject = "IDKIDKIDKTEST"
        _, get_ticket_data_mock, gitlab_comment_issue_mock, gitlab_create_issue_mock = self.prepare_mocks(
            mocker,
            [],
        )
        activities = CreateGitlabIssueOnCertificateTicketActivities(
            GitlabClient(Gitlab(), worker_config.gitlab), RTClient(worker_config.rt), worker_config.rt.base_url
        )
        await test_activity_env.run(activities.create_or_update_gitlab_issue, mock_transaction_obj)

        self.assert_mock_calls(
            None,
            get_ticket_data_mock,
            gitlab_comment_issue_mock,
            gitlab_create_issue_mock,
            gitlab_create_issue_args={
                "title": "Certificate ticket with unrecognized mail format",
                "description": f"There is a [Ticket]({worker_config.rt.base_url}(Ticket/Display.html?id={mock_transaction_obj.ticket_obj.id}) with unrecognized mail format from cesnet.",
            },
        )
