import logging
from datetime import date, datetime, timedelta
from functools import lru_cache
from typing import List
from xml.sax.saxutils import unescape

from epg2xml.providers import EPGProgram, EPGProvider

log = logging.getLogger(__name__.rsplit(".", maxsplit=1)[-1].upper())
today = date.today()


class WAVVE(EPGProvider):
    """EPGProvider for WAVVE

    데이터: jsonapi
    요청수: 1
    특이사항:
    - 해외나 VPS는 차단 가능성이 높음
    """

    referer = "https://www.wavve.com/"
    title_regex = r"^(.*?)(?:\s*[\(<]?([\d]+)회[\)>]?)?(?:\([월화수목금토일]?\))?(\([선별전주\(\)재방]*?재[\d방]?\))?\s*(?:\[(.+)\])?$"

    base_url = "https://apis.wavve.com"
    base_params = {
        "apikey": "E5F3E0D30947AA5440556471321BB6D9",
        "client_version": "6.0.1",
        "device": "pc",
        "drm": "wm",
        "partner": "pooq",
        "pooqzone": "none",
        "region": "kor",
        "targetage": "all",
    }

    def __init__(self, cfg):
        super().__init__(cfg)
        self.sess.headers.update({"wavve-credential": "none"})

    def __url(self, url: str) -> str:
        """completes partial urls from api response or for api request"""
        if url.startswith(("http://", "https://")):
            return url
        if url.startswith("/"):
            return self.base_url + url
        return "https://" + url

    def __params(self, **params) -> dict:
        """returns url parameters for api requests with base ones"""
        p = self.base_params.copy()
        p.update(params)
        return p

    def __get(self, url: str, **kwargs):
        url = self.__url(url)
        params = self.__params(**kwargs.pop("params", {}))
        return self.request(url, params=params, **kwargs)

    def get_svc_channels(self) -> List[dict]:
        today_str = today.strftime("%Y-%m-%d")
        hour_min = datetime.now().hour // 3
        # 현재 시간과 가까운 미래에 서비스 가능한 채널만 가져옴
        params = {
            "enddatetime": f"{today_str} {(hour_min+1)*3:02d}:00",
            "genre": "all",
            "limit": 500,
            "offset": 0,
            "startdatetime": f"{today_str} {hour_min*3:02d}:00",
        }
        return [
            {
                "Name": x["channelname"],
                "Icon_url": self.__url(x["channelimage"]),
                "ServiceId": x["channelid"],
            }
            for x in self.__get("/live/epgs", params=params)["list"]
        ]

    def __epg_of_program(self, channelid: str, data: dict) -> EPGProgram:
        _epg = EPGProgram(channelid)
        _epg.stime = datetime.strptime(data["starttime"], "%Y-%m-%d %H:%M")
        _epg.etime = datetime.strptime(data["endtime"], "%Y-%m-%d %H:%M")
        # 채널이름은 그대로 들어오고 프로그램 제목은 escape되어 들어옴
        _epg.title = unescape(data["title"])
        if m := self.title_regex.match(_epg.title):
            _epg.title = m.group(1)
            _epg.title_sub = m.group(4)
            episode = (m.group(2) or "").replace("회", "").strip()
            _epg.ep_num = None if episode == "0" else episode
            _epg.rebroadcast = bool(m.group(3))
        _epg.rating = 0 if data["targetage"] == "n" else int(data["targetage"])

        # 추가 정보 가져오기
        if not self.cfg["GET_MORE_DETAILS"]:
            return _epg
        programid = data["programid"].strip()
        if not programid:
            # 개별 programid가 없는 경우도 있으니 체크해야함
            return _epg
        detail = self.get_program_details(programid)
        if not detail:
            return _epg
        # 여러가지 추가 정보가 제공되지만
        # 방송되지 않은 미래의 프로그램/에피소드 정보는 반영되지 않았기에
        # 일부 정보만 유효함을 유념
        synopsis = detail["seasonsynopsis"] or detail["programsynopsis"] or detail["episodesynopsis"]
        _epg.desc = "\n".join(
            [x.replace("<br>", "\n").strip() for x in synopsis.splitlines()]
        )  # carriage return(\r) 제거, <br> 제거
        _epg.categories = [detail["genretext"].strip()]
        _epg.poster_url = self.__url(detail["seasonposterimage"].strip())
        _epg.keywords = [x["text"] for x in detail["tags"]["list"]]
        actors = detail.get("season_actors") or detail.get("actors") or {"list": []}
        directors = detail.get("season_directors") or detail.get("directors") or {"list": []}
        writers = detail.get("season_writers") or detail.get("writers") or {"list": []}
        _epg.cast = [{"name": x["text"], "title": "actor"} for x in actors["list"]]
        _epg.crew = [{"name": x["text"], "title": "director"} for x in directors["list"]]
        _epg.crew += [{"name": x["text"], "title": "writer"} for x in writers["list"]]
        return _epg

    def get_programs(self) -> None:
        # parameters for requests
        params = {
            "enddatetime": (today + timedelta(days=int(self.cfg["FETCH_LIMIT"]) - 1)).strftime("%Y-%m-%d 24:00"),
            "genre": "all",
            "limit": 500,
            "offset": 0,
            "startdatetime": today.strftime("%Y-%m-%d 00:00"),
        }
        channeldict = {x["channelid"]: x for x in self.__get("/live/epgs", params=params)["list"]}

        for idx, _ch in enumerate(self.req_channels):
            log.info("%03d/%03d %s", idx + 1, len(self.req_channels), _ch)
            for program in channeldict[_ch.svcid]["list"]:
                try:
                    _epg = self.__epg_of_program(_ch.id, program)
                except Exception:
                    log.exception("프로그램 파싱 중 예외: %s", _ch)
                else:
                    _ch.programs.append(_epg)

    @lru_cache
    def get_program_details(self, programid: str) -> dict:
        try:
            params = {"history": "season", "programid": programid}
            data = self.__get("/fz/vod/programs/landing", params=params)
            if data.get("resultcode") in ["550"]:
                # 애초에 유효하지 않은 programid가 있을 수 있음
                # { "resultcode": "550", "resultmessage": "해당 데이터가 없습니다." }
                return None
            return self.__get(f"/fz/vod/contents-detail/{data['content_id'].strip()}")
        except Exception:
            log.exception("프로그램 상세 정보 요청 중 예외: %s", programid)
            return None
