import re
from abc import ABC, abstractmethod
from functools import cached_property

import httpx
import orjson
from telegram.constants import MessageLimit

from ..cache import CACHES_TIMER, RedisCache
from ..utils import BILI_API, escape_markdown, logger


class Feed(ABC):
    user: str = ""
    uid: str = ""
    __content: str = ""
    __mediaurls: list = []
    mediacontent: dict = {}
    mediaraws: bool = False
    mediatype: str = ""
    mediathumb: str = ""
    mediaduration: int = 0
    mediadimention: dict = {"width": 0, "height": 0, "rotate": 0}
    mediatitle: str = ""
    extra_markdown: str = ""
    replycontent: dict = {}

    def __init__(self, rawurl: str, client: httpx.AsyncClient):
        self.rawurl = rawurl
        self.client = client

    @staticmethod
    def get_filename(url) -> str:
        target = re.search(r"\/([^\/]*\.\w{3,4})(?:$|\?)", url)
        if target:
            return target.group(1)
        return url

    @staticmethod
    def make_user_markdown(user, uid):
        return (
            f"[@{escape_markdown(user)}](https://space.bilibili.com/{uid})"
            if user and uid
            else str()
        )

    @staticmethod
    def shrink_line(text: str):
        return (
            text.strip()
            .replace(
                r"\r\n",
                r"\n",
            )
            .replace(r"\n*\n", r"\n")
            if text
            else str()
        )

    @staticmethod
    def clean_cn_tag_style(content: str) -> str:
        if not content:
            return ""
        ## Refine cn tag style display: #abc# -> #abc
        return re.sub(r"\\#((?:(?!\\#).)+)\\#", r"\\#\1 ", content)

    @cached_property
    def user_markdown(self):
        return self.make_user_markdown(self.user, self.uid)

    @property
    def content(self):
        return self.shrink_line(self.__content)

    @content.setter
    def content(self, content):
        self.__content = content

    @cached_property
    def content_markdown(self):
        content_markdown = escape_markdown(self.content)
        if not content_markdown.endswith("\n"):
            content_markdown += "\n"
        # if self.extra_markdown:
        #     content_markdown += self.extra_markdown
        return self.shrink_line(content_markdown)

    @cached_property
    def comment(self):
        comment = str()
        if isinstance(self.replycontent, dict):
            top = self.replycontent.get("top")
            if top:
                for item in top.values():
                    if item:
                        comment += f'ğŸ”�> @{item["member"]["uname"]}:\n{item["content"]["message"]}\n'
        return self.shrink_line(comment)

    @cached_property
    def comment_markdown(self):
        comment_markdown = str()
        if isinstance(self.replycontent, dict):
            top = self.replycontent.get("top")
            if top:
                for item in top.values():
                    if item:
                        comment_markdown += f'ğŸ”�\\> {self.make_user_markdown(item["member"]["uname"], item["member"]["mid"])}:\n{escape_markdown(item["content"]["message"])}\n'
        return self.shrink_line(comment_markdown)

    @property
    def mediaurls(self):
        return self.__mediaurls

    @mediaurls.setter
    def mediaurls(self, content):
        if isinstance(content, list):
            self.__mediaurls = content
        else:
            self.__mediaurls = [content]

    @cached_property
    def mediafilename(self):
        return (
            [self.get_filename(i) for i in self.__mediaurls]
            if self.__mediaurls
            else list()
        )

    @cached_property
    def mediathumbfilename(self):
        return self.get_filename(self.mediathumb) if self.mediathumb else str()

    @cached_property
    def url(self):
        return self.rawurl

    @cached_property
    def caption(self):
        caption = (
            escape_markdown(self.url)
            if not self.extra_markdown
            else self.extra_markdown + "\n"
        )  # I don't need url twice with extra_markdown
        if self.user:
            caption += self.user_markdown + ":\n"
        prev_caption = caption
        if self.content_markdown:
            caption += (self.clean_cn_tag_style(self.content_markdown)) + "\n"
        if len(caption) > MessageLimit.CAPTION_LENGTH:
            return prev_caption
        prev_caption = caption
        if self.comment_markdown:
            caption += "ã€°ã€°ã€°ã€°ã€°ã€°ã€°ã€°ã€°ã€°\n" + (
                self.clean_cn_tag_style(self.comment_markdown)
            )
        if len(caption) > MessageLimit.CAPTION_LENGTH:
            return prev_caption
        return caption

    async def parse_reply(self, oid, reply_type):
        logger.info(f"å¤„ç�†è¯„è®ºä¿¡æ�¯: è¯„è®ºID: {oid} è¯„è®ºç±»å�‹: {reply_type}")
        # 1.è�·å�–ç¼“å­˜
        try:
            cache = RedisCache().get(f"reply:{oid}:{reply_type}")
        except Exception as e:
            logger.exception(f"æ‹‰å�–è¯„è®ºç¼“å­˜é”™è¯¯: {e}")
            cache = None
        # 2.æ‹‰å�–è¯„è®º
        if cache:
            logger.info(f"æ‹‰å�–è¯„è®ºç¼“å­˜: {oid}")
            reply = orjson.loads(cache)  # type: ignore
        else:
            try:
                r = await self.client.get(
                    BILI_API + "/x/v2/reply/main",
                    params={"oid": oid, "type": reply_type},
                    headers={"Referer": "https://www.bilibili.com/client"},
                )
                response = r.json()
            except Exception as e:
                logger.exception(f"è¯„è®ºè�·å�–é”™è¯¯: {oid}-{reply_type} {e}")
                return {}
            # 3.è¯„è®ºè§£æ��
            if not response or not response.get("data"):
                logger.warning(f"è¯„è®ºè§£æ��é”™è¯¯: {oid}-{reply_type} {response}")
                return {}
            reply = response["data"]
            # 4.ç¼“å­˜è¯„è®º
            try:
                RedisCache().set(
                    f"reply:{oid}:{reply_type}",
                    orjson.dumps(reply),
                    ex=CACHES_TIMER.get("reply"),
                    nx=True,
                )
            except Exception as e:
                logger.exception(f"ç¼“å­˜è¯„è®ºé”™è¯¯: {e}")
        return reply

    @abstractmethod
    async def handle(self):
        return self
