import base64
from collections.abc import Iterator, Sequence
from email.message import Message
from typing import Any, Literal
from urllib.parse import urljoin

import msgspec
import rt.rest2
from rt.exceptions import UnexpectedResponseError
from rt.rest2 import Attachment
from structlog import get_logger

from csirtmu.beta_temporal.config import RTConfig
from csirtmu.beta_temporal.external_services.rt import structs
from csirtmu.beta_temporal.external_services.rt.structs import (
    AssetData,
    AssetObjectFromAPI,
    CatalogObjectFromAPI,
    TicketData,
    TicketObjectFromAPI,
)
from csirtmu.beta_temporal.external_services.rt.utils import parse_headers

logger = get_logger()


class RTClient:
    def __init__(self, rt_config: RTConfig) -> None:
        self.client = rt.rest2.Rt(url=rt_config.host, token=rt_config.token, verify_cert=rt_config.verify_cert)

    def edit_custom_field(self, ticket_id: int, cf_name: str, cf_value: str) -> bool:
        return self.client.edit_ticket(ticket_id, CustomFields={cf_name: cf_value})

    def get_transaction(self, transaction_id: int) -> structs.TransactionObjectFromAPI | None:
        try:
            transaction = self.client.get_transaction(transaction_id)
        except UnexpectedResponseError as e:
            logger.exception(f"Not existant transaction id {transaction_id}!.", e)
            return None
        return msgspec.convert(transaction, type=structs.TransactionObjectFromAPI)

    def get_attachment(self, attachment_id: int) -> structs.AttachmentObjectFromAPI:
        return msgspec.convert(self.client.get_attachment(attachment_id), type=structs.AttachmentObjectFromAPI)

    def comment(
        self,
        ticket_id: int | str,
        content: str,
        content_type: Literal["text/plain", "text/html"] = "text/plain",
        attachments: Sequence[Attachment] | None = None,
    ) -> bool:
        return self.client.comment(ticket_id, content, content_type, attachments)

    def create_ticket(
        self,
        queue: str,
        content_type: Literal["text/plain", "text/html"] = "text/plain",
        subject: str | None = None,
        content: str | None = None,
        attachments: Sequence[Attachment] | None = None,
        **kwargs: Any,
    ) -> int:
        return self.client.create_ticket(queue, content_type, subject, content, attachments, **kwargs)

    def reply(
        self,
        ticket_id: int | str,
        content: str = "",
        content_type: Literal["text/plain", "text/html"] = "text/plain",
        attachments: Sequence[Attachment] | None = None,
    ) -> bool:
        return self.client.reply(ticket_id, content, content_type, attachments)

    def edit_ticket(self, ticket_id: int | str, **kwargs: Any) -> bool:
        return self.client.edit_ticket(ticket_id, **kwargs)

    def edit_link(
        self,
        ticket_id: str | int,
        link_name: Literal["Parent", "Child", "RefersTo", "ReferredToBy", "DependsOn", "DependedOnBy"],
        link_value: str | int,
        delete: bool = False,
    ) -> bool:
        return self.client.edit_link(ticket_id, link_name, link_value, delete)

    def get_attachments(
        self, transaction_data: structs.TransactionObjectFromAPI
    ) -> list[structs.AttachmentObjectFromAPI]:
        attachments: list[structs.AttachmentObjectFromAPI] = []
        for link in transaction_data.hyperlinks:
            if link.ref == "attachment":
                attachments.append(self.get_attachment(int(link.url.split("/")[-1])))
        return attachments

    def get_ticket(self, ticket_id: str | int) -> TicketObjectFromAPI:
        ticket_data: dict[str, Any] = self.client.get_ticket(ticket_id)  # type: ignore
        return msgspec.convert(ticket_data, type=TicketObjectFromAPI)

    def get_ticket_data(self, transaction: structs.TransactionStruct) -> TicketData | None:
        transaction_from_api = self.get_transaction(transaction.id)
        if transaction_from_api is None:
            return None
        attachments = self.get_attachments(transaction_from_api)
        headers = Message()
        body = ""
        if attachments:
            headers = parse_headers(attachments[0].headers)
            body = (
                base64.b64decode(attachments[1].content or "").decode("utf-8")
                if len(attachments) > 1
                else base64.b64decode(attachments[0].content or "").decode("utf-8")
            )
        return TicketData(
            transaction=transaction,
            transaction_from_api=transaction_from_api,
            attachments=attachments,
            headers=headers,
            body=body,
        )

    def search(
        self,
        queue: str | object | None = None,
        order: str | None = None,
        raw_query: str | None = None,
        query_format: str = "l",
        **kwargs: Any,
    ) -> Iterator[dict[str, Any]]:
        return self.client.search(queue, order, raw_query, query_format, **kwargs)

    def get_catalog(self, id_: int | str) -> CatalogObjectFromAPI | None:
        url = str(urljoin(self.client.url, f"catalog/{id_}"))
        response = self.client.session.get(url)

        if response.status_code != 200:
            return None
        return msgspec.convert(response.json(), type=CatalogObjectFromAPI)

    def get_asset_data(self, id_: int | str) -> AssetData | None:
        asset = self.get_asset(id_)
        if not asset:
            return None

        catalog = self.get_catalog(asset.catalog.id)
        if not catalog:
            return None

        return AssetData(
            catalog.name,
            asset.name,
            asset.status,
            asset.description,
            asset.id,
            asset.hyperlinks,
            asset.custom_fields,
        )

    def get_asset(self, id_: int | str) -> AssetObjectFromAPI | None:
        url = str(urljoin(self.client.url, f"asset/{id_}"))
        response = self.client.session.get(url)

        if response.status_code != 200:
            return None
        return msgspec.convert(response.json(), type=AssetObjectFromAPI)

    def create_asset(self, name: str, catalog: str, **kwargs: Any) -> int | None:
        url = str(urljoin(self.client.url, "asset"))
        response = self.client.session.post(url, json={"Name": name, "Catalog": catalog, **kwargs})

        if response.status_code != 201:
            return None
        return response.json()["id"]

    def edit_asset(self, id_: str | int, **kwargs: Any) -> bool:
        url = str(urljoin(self.client.url, f"asset/{id_}"))
        response = self.client.session.put(url, json=kwargs)

        return response.status_code == 200
