import re
from base64 import b64encode
from dataclasses import dataclass
from typing import Dict, List
from xml.etree import ElementTree

from via.services.http import HTTPService


@dataclass
class TranscriptInfo:
    """Information about a YouTube video transcript."""

    language_code: str
    """The transcript's language code, e.g. "en-us"."""

    name: str
    """The transcript's name, e.g. "English (United States)"."""

    url: str
    """The transcript's download URL."""

    autogenerated: bool
    """Whether or not the transcript was autogenerated by speech recognition."""

    @property
    def id(self) -> str:  # pylint:disable=invalid-name
        """Return a unique ID for this transcript."""
        return ".".join(
            [
                self.language_code,
                "a" if self.autogenerated else "",
                b64encode(self.name.encode("utf-8")).decode("utf-8"),
            ]
        ).rstrip(".")


#: The preferences that we apply when choosing a default transcript for a video.
DEFAULT_TRANSCRIPT_PREFERENCES = (
    {
        "language_code": re.compile("en"),
        "name": re.compile("English"),
        "autogenerated": False,
    },
    {
        "language_code": re.compile("en-.*"),
        "name": re.compile(r"English \(.*\)"),
        "autogenerated": False,
    },
    {
        "language_code": re.compile("en"),
        "name": re.compile(".*"),
        "autogenerated": False,
    },
    {
        "language_code": re.compile("en-.*"),
        "name": re.compile(".*"),
        "autogenerated": False,
    },
    {
        "language_code": re.compile("en"),
        "name": re.compile(".*"),
        "autogenerated": True,
    },
    {
        "language_code": re.compile("en-"),
        "name": re.compile(".*"),
        "autogenerated": True,
    },
)


class YouTubeTranscriptService:
    """A service for getting text transcripts of YouTube videos."""

    def __init__(self, http_service: HTTPService):
        self._http_service = http_service

    def get_transcript_infos(self, video_id: str) -> List[TranscriptInfo]:
        """Return the list of available transcripts for `video_id`."""
        response = self._http_service.post(
            "https://youtubei.googleapis.com/youtubei/v1/player",
            json={
                "context": {
                    "client": {
                        "hl": "en",
                        "clientName": "WEB",
                        "clientVersion": "2.20210721.00.00",
                    }
                },
                "videoId": video_id,
            },
        )
        json = response.json()
        dicts = json["captions"]["playerCaptionsTracklistRenderer"]["captionTracks"]

        return [
            TranscriptInfo(
                language_code=caption_track_dict["languageCode"].lower(),
                autogenerated=caption_track_dict.get("kind", None) == "asr",
                name=caption_track_dict["name"]["simpleText"],
                url=caption_track_dict["baseUrl"],
            )
            for caption_track_dict in dicts
        ]

    def pick_default_transcript(
        self, transcript_infos: List[TranscriptInfo]
    ) -> TranscriptInfo:
        """Return a choice of default transcript from `transcript_infos`."""

        def matches(transcript_info: TranscriptInfo, preference: dict):
            for key in ("language_code", "name"):
                if not preference[key].fullmatch(getattr(transcript_info, key)):
                    return False

            return preference["autogenerated"] == transcript_info.autogenerated

        for preference in DEFAULT_TRANSCRIPT_PREFERENCES:
            for transcript_info in transcript_infos:
                if matches(transcript_info, preference):
                    return transcript_info

        return transcript_infos[0]

    def get_transcript(self, transcript_info: TranscriptInfo) -> List[Dict]:
        """Download and return the actual transcript text for `transcript_info`."""
        response = self._http_service.get(transcript_info.url)
        xml_elements = ElementTree.fromstring(response.text)

        def strip_html(xml_string):
            return "".join(
                ElementTree.fromstring(f"<span>{xml_string}</span>").itertext()
            ).strip()

        return [
            {
                "text": strip_html(xml_element.text),
                "start": float(xml_element.attrib["start"]),
                "duration": float(xml_element.attrib.get("dur", "0.0")),
            }
            for xml_element in xml_elements
            if xml_element.text is not None
        ]


def factory(_context, request):
    return YouTubeTranscriptService(http_service=request.find_service(HTTPService))
