from dataclasses import dataclass
from typing import Any

import narwhals as nw
import nh3

from ssb_pubmd.adapters.cms_client.http_client import HttpClient
from ssb_pubmd.adapters.cms_client.http_client import RequestsHttpClient
from ssb_pubmd.adapters.cms_client.models import CmsRequest
from ssb_pubmd.adapters.cms_client.models import CmsResponse
from ssb_pubmd.adapters.cms_client.token_client import DaplaTokenClient
from ssb_pubmd.adapters.cms_client.token_client import TokenClient
from ssb_pubmd.common.exceptions import CmsClientError
from ssb_pubmd.models import Article
from ssb_pubmd.models import Factbox
from ssb_pubmd.models import Highchart
from ssb_pubmd.models import UserData

DEFAULT_HTTP_CLIENT = RequestsHttpClient()
DEFAULT_TOKEN_CLIENT = DaplaTokenClient()
ARTICLE_HTML_TAGS = {
    "p",
    "br",
    "strong",
    "em",
    "b",
    "i",
    "ul",
    "ol",
    "li",
    "blockquote",
    "h2",
    "h3",
    "h4",
    "h5",
    "a",
}

FACTBOX_HTML_TAGS = ARTICLE_HTML_TAGS - {"h2"}


@dataclass
class MimirCmsClient:
    base_url: str
    admin_path: str
    http_client: HttpClient = DEFAULT_HTTP_CLIENT
    token_client: TokenClient = DEFAULT_TOKEN_CLIENT

    def send(self, cms_request: CmsRequest) -> CmsResponse:
        response_body = self.http_client.post(
            url=f"{self.base_url}/webapp/pubmd",
            headers=self._create_headers(),
            payload=self._create_payload(cms_request),
        )

        content_id = response_body.get("_id")
        content_path = response_body.get("_path")

        if content_path is None or content_id is None:
            raise CmsClientError("Sync failed. Fields missing in response body.")

        return CmsResponse(
            content_path=content_path,
            content_id=content_id,
            preview_url=self.base_url
            + self.admin_path
            + "/site/preview/default/draft"
            + content_path,
            html_snippet=self._create_html_snippet(content_id, cms_request.user_data),
        )

    def _create_headers(self) -> dict[str, str]:
        return {
            "Authorization": f"Bearer {self.token_client.get_token()}",
            "Content-Type": "application/json",
        }

    def _create_payload(self, cms_request: CmsRequest) -> dict[str, Any]:
        """Reference: https://github.com/statisticsnorway/ssbno-app-pubmd/blob/main/src/main/resources/lib/types.ts."""
        user_data = cms_request.user_data

        payload: dict[str, Any] = {
            "displayName": user_data.title,
            "parentPath": cms_request.target_folder,
        }
        if cms_request.content_id is not None:
            payload["_id"] = cms_request.content_id

        if isinstance(user_data, Article):
            payload["contentType"] = "mimir:article"
            payload["parentPath"] = self._normalize_path(cms_request.target_folder)
            payload["data"] = self._create_article_payload(user_data, cms_request.html)
        elif isinstance(user_data, Factbox):
            payload["contentType"] = "mimir:factBox"
            payload["data"] = self._create_factbox_payload(user_data, cms_request.html)
        elif isinstance(user_data, Highchart):
            payload["contentType"] = "mimir:highchart"
            payload["data"] = self._create_highchart_payload(user_data)

        return payload

    def _normalize_path(self, path: str) -> str:
        if not path.startswith("/"):
            path = "/" + path
        if not path.startswith("/ssb/"):
            path = "/ssb" + path
        return path

    def _create_article_payload(self, data: Article, html: str) -> dict[str, Any]:
        """Reference: https://github.com/statisticsnorway/mimir/blob/develop/src/main/resources/site/content-types/article/article.xml."""
        payload: dict[str, Any] = {}
        payload["authorItemSet"] = [a.model_dump() for a in data.authors]
        payload["ingress"] = data.ingress
        payload["articleText"] = nh3.clean(html, tags=ARTICLE_HTML_TAGS)
        return payload

    def _create_factbox_payload(self, data: Factbox, html: str) -> dict[str, Any]:
        """Reference: https://github.com/statisticsnorway/mimir/blob/develop/src/main/resources/site/content-types/factBox/factBox.xml."""
        payload: dict[str, Any] = {}
        payload["expansionBoxType"] = data.display_type
        payload["text"] = nh3.clean(html, FACTBOX_HTML_TAGS)
        return payload

    def _create_highchart_payload(self, data: Highchart) -> dict[str, Any]:
        """Reference: https://github.com/statisticsnorway/mimir/blob/develop/src/main/resources/site/content-types/highchart/highchart.xml."""
        payload: dict[str, Any] = {}
        payload["graphType"] = data.graph_type
        payload["xAxisTitle"] = data.xlabel
        payload["yAxisTitle"] = data.ylabel

        if data.dataframe is not None:
            payload["htmlTable"] = self._dataframe_to_html_table(data.dataframe)
        elif data.tbml is not None:
            payload["dataSource"] = {
                "_selected": "tbprocessor",
                "tbprocessor": {"urlOrId": data.tbml},
            }

        return payload

    def _dataframe_to_html_table(self, dataframe: Any) -> str:
        """Converts a dataframe to a plain html table.

        Notes:
            - The 'mimir:highchart' type expects a plain html table without special formatting, with column names in first row.

        Reference: https://github.com/statisticsnorway/mimir/blob/develop/src/main/resources/site/content-types/highchart/highchart.xml
        """
        df = nw.from_native(dataframe)

        html = "<table><tbody>"

        html += "<tr>"
        for name in df.columns:
            html += f"<td>{name}</td>"
        html += "</tr>\n"

        for row in df.iter_rows():
            html += "<tr>"
            for value in row:
                html += f"<td>{value}</td>"
            html += "</tr>"

        html += "</tbody></table>"

        return html

    def _create_html_snippet(self, content_id: str, user_data: UserData) -> str:
        """A html snippet for the content that can be parsed and rendered by Enonic CMS.

        References:
            - https://developer.enonic.com/docs/xp/stable/cms/macros#macro_instructions
            - https://github.com/statisticsnorway/mimir/blob/develop/src/main/resources/site/macros/highchart/highchart.xml
            - https://github.com/statisticsnorway/mimir/blob/develop/src/main/resources/site/macros/factBox/factBox.xml
        """
        if isinstance(user_data, Factbox):
            return f"<p>[ factBox factBox=&quot;{content_id}&quot; /]</p>"
        elif isinstance(user_data, Highchart):
            return f"<p>[ highchart highchart=&quot;{content_id}&quot; /]</p>"
        else:
            return ""
