import re
from collections.abc import Awaitable, Callable, Sequence
from typing import Any

from gitlab import Gitlab
from temporalio import activity, workflow

from csirtmu.beta_temporal.commons.conditions import CommonConditions
from csirtmu.beta_temporal.config import AppConfig
from csirtmu.beta_temporal.constants import WorkflowReference
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.logging import LoggedActivities, LoggedWorkflow


class CreateGitlabIssueOnCertificateTicketActivities(LoggedActivities):
    def __init__(self, gitlab: GitlabClient, rt_client: RTClient, rt_url: str) -> None:
        self.gitlab = gitlab
        self.rt_client = rt_client
        self.rt_url = rt_url

    def get_activities(self) -> Sequence[Callable[..., Any]]:
        return [self.create_or_update_gitlab_issue]

    def check_if_gitlab_issue_exists(self, cn: str) -> int | None:
        for issue in self.gitlab.get_open_issues():
            if cn in issue.title:
                return issue.iid
        return None

    def comment_cert_expiration_notification(self, ticket_id: int, issue_iid: int) -> None:
        self.gitlab.comment_issue(
            issue_iid,
            f"New expiration notification received: [Ticket]({self.rt_url}Ticket/Display.html?id={ticket_id})",
        )

    def comment_cert_request_confirmed(self, ticket_id: int, issue_iid: int, cn: str) -> None:
        self.gitlab.comment_issue(
            issue_iid,
            f"Received confirmation for certificate request for `{cn}`: [Ticket]({self.rt_url}Ticket/Display.html?id={ticket_id})",
        )

    def comment_cert_request_approved(self, ticket_id: int, issue_iid: int) -> None:
        self.gitlab.comment_issue(
            issue_iid,
            f"Received certificate request approval: [Ticket]({self.rt_url}Ticket/Display.html?id={ticket_id})",
        )

    def comment_cert_released(self, ticket_id: int, issue_iid: int) -> None:
        self.gitlab.comment_issue(
            issue_iid, f"New certificate was released: [Ticket]({self.rt_url}Ticket/Display.html?id={ticket_id})"
        )

    def extract_cn(self, transaction: structs.TransactionStruct) -> str | None:
        if (data := self.rt_client.get_ticket_data(transaction)) is None:
            raise RuntimeError("Cant get ticket data from RT")
        regex = r"^SUBJECT:.*CN=(\S+),.*$"
        for line in data.body.splitlines():
            if (m := re.match(regex, line)) is not None:
                return m.group(1)
        return None

    def get_or_create_issue(self, cn: str, ticket_id: int) -> int:
        if (issue_iid := self.check_if_gitlab_issue_exists(cn)) is None:
            issue_iid = self.gitlab.create_issue(
                title=f"Expiration of certificate {cn}",
                description=f"Expiration notification received for CN `{cn}` [Ticket]({self.rt_url}Ticket/Display.html?id={ticket_id})",
            )
        return issue_iid

    def check_and_call_helper(
        self,
        regex: str,
        subject: str,
        function: Callable[[int, int], None],
        ticket_id: int,
    ) -> bool:
        m = re.match(regex, subject)
        if m is not None:
            function(ticket_id, self.get_or_create_issue(m.group(2), ticket_id))
            return True
        return False

    def create_issue_for_unknown_mail_format(self, ticket_id: int) -> None:
        self.gitlab.create_issue(
            title="Certificate ticket with unrecognized mail format",
            description=f"There is a [Ticket]({self.rt_url}(Ticket/Display.html?id={ticket_id}) with unrecognized mail format from cesnet.",
        )

    @activity.defn
    async def create_or_update_gitlab_issue(self, transaction: structs.TransactionStruct) -> None:
        self.log_activity()
        if not transaction.ticket_obj:
            return None
        ticket_id = transaction.ticket_obj.id
        subject = transaction.ticket_obj.subject
        if self.check_and_call_helper(
            r"Schv.len. ..dosti o certifik.t TCS / A TCS Certificate Request Has Been Accepted #(\d+) \((\S+)\)",
            subject,
            self.comment_cert_request_approved,
            ticket_id,
        ):
            return None
        if self.check_and_call_helper(
            r"Ozn.men. o vyd.n. certifik.tu TCS / A TCS Certificate Has Been Issued #(\d+) \((\S+)\)",
            subject,
            self.comment_cert_released,
            ticket_id,
        ):
            return None
        m = re.match(r"Expirace certifik.tu (\S+) \(TCS3\)", subject)
        if m is not None:
            cn = m.group(1)
            if (issue_iid := self.check_if_gitlab_issue_exists(cn)) is not None:
                return self.comment_cert_expiration_notification(ticket_id, issue_iid)
            self.gitlab.create_issue(
                title=f"Expiration of certificate {cn}",
                description=f"Expiration notification received for CN `{cn}` [Ticket]({self.rt_url}Ticket/Display.html?id={ticket_id})",
            )
            return None
        m = re.match(
            r"Confirmation of TCS certificate request submission|Potvrzen. pod.n. ..dosti o certifik.t TCS", subject
        )
        if m is not None and (cn := self.extract_cn(transaction)) is not None:
            self.comment_cert_request_confirmed(ticket_id, self.get_or_create_issue(cn, ticket_id), cn)
            return None
        return self.create_issue_for_unknown_mail_format(ticket_id)


class CreateGitlabIssueOnCertificateTicket(
    LoggedWorkflow, workflow_name=WorkflowReference.CREATE_GITLAB_ISSUE_ON_CERTIFICATE_TICKET
):
    @classmethod
    def get_activities(cls) -> Sequence[Callable[..., Awaitable[Any]]]:
        config = AppConfig.get()
        rt_client = RTClient(config.rt)
        common = CommonConditions(rt_client)
        local = CreateGitlabIssueOnCertificateTicketActivities(
            GitlabClient(Gitlab(config.gitlab.url, config.gitlab.token), config.gitlab), rt_client, config.rt.base_url
        )
        return [*common.get_activities(), *local.get_activities()]

    @workflow.run
    async def run(self, transaction: structs.TransactionStruct) -> None:
        self.log_workflow()
        config = AppConfig.get()
        if await workflow.execute_activity(
            CommonConditions.is_certificate_notification,
            args=(transaction,),
            retry_policy=config.temporal.retry_policy,
            start_to_close_timeout=config.temporal.activity_timeout,
        ):
            await workflow.execute_activity(
                CreateGitlabIssueOnCertificateTicketActivities.create_or_update_gitlab_issue,
                args=(transaction,),
                retry_policy=config.temporal.retry_policy,
                start_to_close_timeout=config.temporal.activity_timeout,
            )
