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

from temporalio import activity, workflow

from csirtmu.beta_temporal.commons.actions.temporal_common_actions import TemporalCommonActions
from csirtmu.beta_temporal.config import AppConfig
from csirtmu.beta_temporal.constants import WorkflowReference
from csirtmu.beta_temporal.external_services.alpha.client import AlphaClient
from csirtmu.beta_temporal.external_services.alpha.utils import get_culprit_based_on_ip
from csirtmu.beta_temporal.external_services.data_platform.client import TrinoClient
from csirtmu.beta_temporal.external_services.rt import structs
from csirtmu.beta_temporal.external_services.rt.client import RTClient
from csirtmu.beta_temporal.external_services.rt.structs import TicketData, TransactionStruct
from csirtmu.beta_temporal.external_services.rt.utils import (
    CopyrightDetails,
    create_copyright_notice_ticket,
    extract_xml_from_body,
    get_details_from_xml,
    get_header,
    parse_xml_from_attachment,
    search_for_keyword_in_attachments,
    try_translate_ip_address_to_domain,
)
from csirtmu.beta_temporal.workflows.logging import LoggedActivities, LoggedWorkflow


class CopyrightHandlingActivities(LoggedActivities):
    USER_SEARCH_ERROR: str = "Nepodarilo se dohledat uzivatele, zaviram. Duvod: chyba."
    UNKNOWN_USER: str = "Nenalezen zadny uzivatel, zaviram."
    COPYRIGHT_REPLY: str = """<p>Dear Sir or Madam,<br/>

thank you for reporting the issue. We look forward to solving your problem promptly.
<br/><br/>

Best regards,<br/>
CSIRT-MU, Masaryk University Cybersecurity team<br/>
<a href=https://csirt.muni.cz/?lang=en>https://csirt.muni.cz</a></p>
"""
    COPYRIGHT_TICKET_2_SUBJECT: str = (
        "Porušování autorských práv z účtu {mail} / Copyright infringement from the account {mail}"
    )

    def __init__(self, data_platform_url: str, rt_client: RTClient, alpha_client: AlphaClient) -> None:
        self.data_platform_url = data_platform_url
        self.rt_client = rt_client
        self.alpha_client = alpha_client

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

    @activity.defn
    async def is_copyright_notice(self, transaction: TransactionStruct) -> bool:
        self.log_activity()
        if (data := self.rt_client.get_ticket_data(transaction)) is None:
            return False
        if not data.attachments or "devel" in data.transaction.creator.id:
            return False
        senders = get_header(data.headers, "From")
        if any("csirt@muni.cz" in sender.lower() for sender in senders):
            return False
        subjects = get_header(data.headers, "Subject")
        if (
            any("copyright" in sender.lower() for sender in senders)
            or any("copyright" in subject.lower() for subject in subjects)
            or any("infringement" in subject.lower() for subject in subjects)
        ):
            if (data.body and "Start ACNS XML" in data.body) or search_for_keyword_in_attachments(
                data.attachments, "xml"
            ):
                return True
            if any("certs@cesnet.cz" in sender for sender in senders):
                return False
        return False

    def get_details_from_xml(self, data: TicketData) -> CopyrightDetails:
        xml: dict[str, Any] = {}
        if data.body and "Start ACNS XML" in data.body:
            xml = extract_xml_from_body(data.body)
        if search_for_keyword_in_attachments(data.attachments, "xml"):
            xml = parse_xml_from_attachment(data.attachments)
        if data.transaction.ticket_obj is None:
            raise ValueError(f"TicketObj in transaction: {data.transaction} is None.")
        self.rt_client.edit_custom_field(data.transaction.ticket_obj.id, "Category", "Automatic - Copyright notice")
        return get_details_from_xml(xml.get("Infringement", {}))

    def set_cf_on_details(self, ticket_id: int, details: CopyrightDetails) -> None:
        if details.culprit_ip:
            self.rt_client.edit_custom_field(ticket_id, "IP", details.culprit_ip)
        if details.infringing_time:
            self.rt_client.edit_custom_field(ticket_id, "Summary", details.infringing_time)

    def try_translate_ip(self, ticket_id: int, details: CopyrightDetails) -> str | None:
        if not details.culprit_ip or not details.infringing_time or not details.port:
            return None
        time = datetime.datetime.fromisoformat(details.infringing_time)
        translated_ip: list[str] = TrinoClient(self.data_platform_url).get_nat_translation(
            details.culprit_ip,
            details.port,
            time - datetime.timedelta(minutes=2),
            time + datetime.timedelta(minutes=2),
        )
        if len(translated_ip) == 0:
            text = f"Unable to translate public IP: {details.culprit_ip}"
        else:
            text = f"Public IP: {details.culprit_ip} translated to "
            if len(translated_ip) == 1:
                text += f"address: {translated_ip[0]}"
            else:
                text += f"multiple addresses: {translated_ip}"
        self.rt_client.comment(ticket_id, text, "text/plain")
        return translated_ip[0] if len(translated_ip) == 1 else None

    @activity.defn
    async def actions_on_copyright_notice(self, transaction: TransactionStruct) -> None:
        self.log_activity()
        if (data := self.rt_client.get_ticket_data(transaction)) is None or data.transaction.ticket_obj is None:
            return
        details = self.get_details_from_xml(data)
        self.set_cf_on_details(data.transaction.ticket_obj.id, details)
        user_uco = None
        mail = None
        dns = "<translated_domain_placeholder>"
        new_ticket_kwargs: dict[str, Any] = {
            "CustomFields": {"Category": "Automatic - Copyright notice"},
            "Status": "act",
        }
        if (nat_ip := self.try_translate_ip(data.transaction.ticket_obj.id, details)) is not None:
            if (culprit := await get_culprit_based_on_ip(nat_ip, details.infringing_time, self.alpha_client)) is None:
                self.rt_client.comment(data.transaction.ticket_obj.id, self.UNKNOWN_USER, "text/plain")
            else:
                if re.match(r"^\d+$|^\d+@(\w+\.)?muni\.cz$", culprit.username or ""):
                    match = re.match(r"(\d+)(@.*)?", culprit.username or "")
                    user_uco = match.group(1) if match is not None else ""
                    mail = f"{user_uco}@mail.muni.cz"
                else:
                    mail = culprit.username
                dns = try_translate_ip_address_to_domain(details.culprit_ip) or "---"
                new_ticket_kwargs["Status"] = "Resolved"
                new_ticket_kwargs["Requestor"] = [mail or "<email_placeholder>"]
                new_ticket_kwargs["CustomFields"]["UCO"] = user_uco or mail or "<email_placeholder>"
        new_ticket_id = self.rt_client.create_ticket(
            data.transaction.queue,
            "text/html",
            self.COPYRIGHT_TICKET_2_SUBJECT.format(mail=(mail or "<email_placeholder>")),
            create_copyright_notice_ticket(
                mail or "<email_placeholder>",
                nat_ip or "<translated_ip_placeholder>",
                details.infringing_time,
                dns,
                details.infringing_file,
            ),
            **new_ticket_kwargs,
        )
        self.rt_client.edit_link(new_ticket_id, "Parent", data.transaction.ticket_obj.id)
        self.rt_client.reply(data.transaction.ticket_obj.id, self.COPYRIGHT_REPLY, "text/html")
        if user_uco is not None:
            self.rt_client.edit_custom_field(data.transaction.ticket_obj.id, "UCO", user_uco)
        elif mail is not None:
            self.rt_client.edit_custom_field(data.transaction.ticket_obj.id, "UCO", mail)
        self.rt_client.edit_custom_field(data.transaction.ticket_obj.id, "Duplicity", "True")
        if transaction.ticket_obj is not None:
            self.rt_client.edit_ticket(transaction.ticket_obj.id, Status="Resolved")


class HandleCopyrightInfringement(LoggedWorkflow, workflow_name=WorkflowReference.HANDLE_COPYRIGHT_INFRINGEMENT):
    @classmethod
    def get_activities(cls) -> Sequence[Callable[..., Awaitable[Any]]]:
        config = AppConfig.get()
        copyright_activities = CopyrightHandlingActivities(
            config.data_platform.url, RTClient(config.rt), AlphaClient(config.alpha)
        )
        temporal_actions = TemporalCommonActions(config.temporal)
        return [*copyright_activities.get_activities(), *temporal_actions.get_activities()]

    @workflow.run
    async def run(self, transaction: structs.TransactionStruct) -> None:
        self.log_workflow()
        config = AppConfig.get()
        if await workflow.execute_activity(
            CopyrightHandlingActivities.is_copyright_notice,
            args=(transaction,),
            retry_policy=config.temporal.retry_policy,
            start_to_close_timeout=config.temporal.activity_timeout,
        ):
            await workflow.execute_activity(
                CopyrightHandlingActivities.actions_on_copyright_notice,
                args=(transaction,),
                retry_policy=config.temporal.retry_policy,
                start_to_close_timeout=config.temporal.activity_timeout,
            )
        elif config.temporal.allow_internal_cancels:
            await workflow.execute_activity(
                TemporalCommonActions.cancel_workflow,
                args=(workflow.info().workflow_id, workflow.info().task_queue),
                retry_policy=config.temporal.retry_policy,
                start_to_close_timeout=config.temporal.activity_timeout,
            )
