from collections.abc import Mapping
from dataclasses import asdict
from dataclasses import dataclass
from typing import Any
from typing import Literal
from typing import Protocol

import nh3


@dataclass
class Content:
    title: str
    content_type: str
    publish_folder: str | None = None
    publish_id: str | None = None

    def to_dict(self) -> dict[str, Any]:
        return asdict(self)

    def serialize(self) -> dict[str, Any]:
        raise NotImplementedError()

class ContentParser(Protocol):
    def parse(self, metadata: Mapping[str, Any], html: str | None) -> Content: ...


@dataclass
class MimirContent(Content):
    def is_publishable(self) -> bool:
        if self.title == "":
            return False
        if self.publish_id is None and self.publish_folder is None:
            return False
        return True

    def serialize(self) -> dict[str, Any]:
        if not self.is_publishable():
            raise Exception()
        s: dict[str, Any] = {
            "contentType": "mimir:" + self.content_type,
            "displayName": self.title,
            "parentPath": self.publish_folder,
            "data": {},
        }
        if self.publish_id is not None:
            s["_id"] = self.publish_id
        return s
    

@dataclass
class Author:
    name: str
    email: str

@dataclass
class Article(MimirContent):
    content_type: str = "article"
    authors: list[Author] | None = None
    ingress: str = ""
    html_text: str = ""

    def serialize(self) -> dict[str, Any]:
        s = super().serialize()
        if self.authors:
            s["data"]["authorItemSet"] = [asdict(author) for author in self.authors]
        s["data"]["ingress"] = self.ingress
        s["data"]["articleText"] = self.html_text
        return s


GraphType = Literal["line", "pie", "column", "bar", "area", "barNegative"]


@dataclass
class Highchart(MimirContent):
    content_type: str = "highchart"
    graph_type: GraphType = "line"
    html_table: str | None = None
    tbml: str | None = None
    xlabel: str = "x"
    ylabel: str = "y"

    def serialize(self) -> dict[str, Any]:
        s = super().serialize()

        if self.html_table is not None:
            s["data"]["htmlTable"] = self.html_table
        elif self.tbml is not None:
            s["data"]["dataSource"] = {
                "_selected": "tbprocessor",
                "tbprocessor": {"urlOrId": self.tbml},
            }

        s["data"]["xAxisTitle"] = self.xlabel
        s["data"]["yAxisTitle"] = self.ylabel

        return s


@dataclass
class FactBox(MimirContent):
    content_type: str = "factBox"
    display_type: Literal["default", "sneakPeek", "aiIcon"] = "default"
    html_text: str = ""

    def serialize(self) -> dict[str, Any]:
        s = super().serialize()
        s["data"]["expansionBoxType"] = self.display_type
        s["data"]["text"] = self.html_text
        return s


BASIC_HTML_TAGS = {
    "p",
    "br",
    "strong",
    "em",
    "b",
    "i",
    "ul",
    "ol",
    "li",
    "blockquote",
    "h1",
    "h2",
    "h3",
    "h4",
    "h5",
    "a",
}


class MimirContentParser:
    def parse(self, metadata: Mapping[str, Any], html: str | None) -> Content:
        match metadata.get("content_type"):
            case "article":
                return self._parse_article(metadata, html)
            case "factBox":
                return self._parse_factbox(metadata, html)
            case "highchart":
                return self._parse_highchart(metadata, html)
            case _:
                return MimirContent(**metadata)

    def serialize(self, content: Content) -> dict[str, Any]:
        if isinstance(content, MimirContent):
            return content.serialize()
        else:
            raise Exception()

    @classmethod
    def _parse_article(cls, metadata: Mapping[str, Any], html: str | None) -> Article:
        article = Article(
            title=metadata["title"],
            publish_folder="/ssb" + metadata["path"],
            publish_id=metadata.get("publish_id"),
            authors=[Author(**data) for data in metadata.get("authors", [])],
            ingress=metadata.get("ingress", ""),
        )
        if html is not None:
            allowed_html_tags = BASIC_HTML_TAGS
            html_text = nh3.clean(html, tags=allowed_html_tags)
            article.html_text = html_text
        return article

    @classmethod
    def _parse_factbox(cls, metadata: Mapping[str, Any], html: str | None) -> FactBox:
        factbox = FactBox(**metadata)
        if html is not None:
            allowed_html_tags = BASIC_HTML_TAGS - {"h2"}
            html_text = nh3.clean(html, tags=allowed_html_tags)
            factbox.html_text = html_text
        return factbox

    @classmethod
    def _parse_highchart(
        cls, metadata: Mapping[str, Any], html: str | None
    ) -> Highchart:
        highchart = Highchart(**metadata)
        if html is not None:
            allowed_html_tags = {"table", "tbody", "tr", "td"}
            html_table = nh3.clean(html, tags=allowed_html_tags)
            highchart.html_table = html_table
        return highchart