from __future__ import annotations

from html.parser import HTMLParser
from urllib.parse import urlparse


_ALLOWED_TAGS = {
    "b", "strong", "i", "em", "u", "ins", "s", "strike", "del",
    "span", "tg-spoiler", "a", "code", "pre", "blockquote", "tg-emoji",
}
_VOID_TAGS = {"br"}


class _HTMLValidator(HTMLParser):
    def __init__(self) -> None:
        super().__init__(convert_charrefs=True)
        self.stack: list[str] = []

    @staticmethod
    def _canonical(tag: str) -> str:
        aliases = {
            "strong": "b", "em": "i", "ins": "u",
            "strike": "s", "del": "s",
        }
        return aliases.get(tag, tag)

    def handle_starttag(self, tag: str, attrs) -> None:
        tag = tag.lower()
        canonical = self._canonical(tag)
        if canonical in _VOID_TAGS:
            return
        if canonical not in _ALLOWED_TAGS:
            raise ValueError(f"Tag <{tag}> tidak didukung Telegram")

        attrs_dict = dict(attrs)
        if canonical == "tg-emoji":
            emoji_id = attrs_dict.get("emoji-id")
            if not emoji_id or not str(emoji_id).isdigit():
                raise ValueError("Tag tg-emoji wajib memiliki emoji-id berupa angka")
        if canonical == "a" and not attrs_dict.get("href"):
            raise ValueError("Tag link <a> wajib memiliki href")
        if canonical == "span" and attrs_dict.get("class") != "tg-spoiler":
            raise ValueError("Tag span hanya didukung untuk class tg-spoiler")

        self.stack.append(canonical)

    def handle_startendtag(self, tag: str, attrs) -> None:
        if tag.lower() not in _VOID_TAGS:
            self.handle_starttag(tag, attrs)
            self.handle_endtag(tag)

    def handle_endtag(self, tag: str) -> None:
        canonical = self._canonical(tag.lower())
        if canonical in _VOID_TAGS:
            return
        if not self.stack:
            raise ValueError(f"Tag penutup </{tag}> tidak memiliki pembuka")
        expected = self.stack[-1]
        if expected != canonical:
            raise ValueError(
                f"Urutan tag salah: seharusnya menutup </{expected}> sebelum </{tag}>"
            )
        self.stack.pop()

    def close(self) -> None:
        super().close()
        if self.stack:
            raise ValueError(f"Tag <{self.stack[-1]}> belum ditutup")


def validate_html(value: str) -> str:
    try:
        parser = _HTMLValidator()
        parser.feed(value)
        parser.close()
    except Exception as exc:
        raise ValueError(f"Format HTML tidak valid: {exc}") from exc
    return value


def validate_url(value: str, allow_empty: bool = False) -> str:
    value = value.strip()
    if allow_empty and not value:
        return value
    parsed = urlparse(value)
    if parsed.scheme not in {"http", "https", "tg"}:
        raise ValueError("URL harus diawali http://, https://, atau tg://")
    if parsed.scheme in {"http", "https"} and not parsed.netloc:
        raise ValueError("URL tidak valid")
    return value


def validate_chat_id(value: str) -> str:
    value = value.strip()
    if value.startswith("@") and len(value) > 1:
        return value
    try:
        int(value)
    except ValueError as exc:
        raise ValueError("Chat ID harus berupa -100... atau username @channel") from exc
    return value
