import base64
import json
import click
import re
from requests import JSONDecodeError
from httpx import URL
import uuid
import xmltodict

import time
from datetime import datetime
from langcodes import Language
from vinetrimmer.objects import AudioTrack, TextTrack, Title, Tracks, VideoTrack
from vinetrimmer.services.BaseService import BaseService
from vinetrimmer.vendor.pymp4.parser import Box


class MoviesAnywhere(BaseService):
    """
    Service code for US' streaming service MoviesAnywhere (https://moviesanywhere.com).

    \b
    Authorization: Cookies
    Security: SD-HD@L3, FHD SDR@L1 (any active device), FHD-UHD HDR-DV@L1 (whitelisted devices).

    NOTE: Can be accessed from any region, it does not seem to care.
          Accounts can only mount services when its US based though.

    """
    ALIASES = ["MA", "moviesanywhere"]

    TITLE_RE = r"https://moviesanywhere\.com(?P<id>.+)"

    VIDEO_CODEC_MAP = {
        "H264": ["avc"],
        "H265": ["hvc", "hev", "dvh"]
    }
    AUDIO_CODEC_MAP = {
        "AAC": ["mp4a", "HE", "stereo"],
        "AC3": ["ac3"],
        "EC3": ["ec3", "atmos"]
    }

    @staticmethod
    @click.command(name="MoviesAnywhere", short_help="moviesanywhere.com")
    @click.argument("title", type=str)
   
    @click.pass_context
    def cli(ctx, **kwargs):
        return MoviesAnywhere(ctx, **kwargs)

    def __init__(self, ctx, title):
        super().__init__(ctx)
        self.parse_title(ctx, title)
        self.configure()

        self.atmos = ctx.parent.params["atmos"]
        self.vcodec = ctx.parent.params["vcodec"]
        self.acodec = ctx.parent.params["acodec"]

    def get_titles(self):
        self.headers={
            "authorization": f"Bearer {self.access_token}",
            "install-id": self.install_id,
        }
        res = self.session.post(
            url="https://gateway.moviesanywhere.com/graphql",
            json={
                "platform": "web",
                "variables": {"slug": self.title}, # Does not seem to care which platform will be used to give the best tracks available
                "extensions": '{"persistedQuery":{"sha256Hash":"5cb001491262214406acf8237ea2b8b46ca6dbcf37e70e791761402f4f74336e","version":1}}',  # ONE_GRAPH_PERSIST_QUERY_TOKEN
            },
            headers={
                "authorization": f"Bearer {self.access_token}",
                "install-id": self.install_id,
            }
        )

        try:
            self.content = res.json()
        except JSONDecodeError:
            self.log.exit(" - Not able to return title information")

        title_data = self.content["data"]["page"]

        title_info = [
            x
            for x in title_data["components"]
            if x["__typename"] == "MovieMarqueeComponent"
        ][0]
        
        title_info["title"] = re.sub(r" \(.+?\)", "", title_info["title"])

        title_data = self.content["data"]["page"]
        try:
            Id = title_data["components"][0]["mainAction"]["playerData"]["playable"]["id"]
        except KeyError:
            self.log.exit(" - Account does not seem to own this title")
        
        return Title(
                id_=Id,
                type_=Title.Types.MOVIE,
                name=title_info["title"],
                year=title_info["year"],
                original_lang="en",
                source=self.ALIASES[0],
                service_data=title_data,
            )
    
    def get_pssh_init(self, url):
        import os, yt_dlp
        from pathlib import Path
        init = 'init.mp4'

        files_to_delete = [init]
        for file_name in files_to_delete:
            if os.path.exists(file_name):
                os.remove(file_name)

        def read_pssh(path: str):
            raw = Path(path).read_bytes()
            wv = raw.rfind(bytes.fromhex('edef8ba979d64acea3c827dcd51d21ed'))
            if wv == -1: return None
            return base64.b64encode(raw[wv-12:wv-12+raw[wv-9]]).decode('utf-8')
    
        ydl_opts = {
            'format': 'bestvideo[ext=mp4]/bestaudio[ext=m4a]/best',
            'allow_unplayable_formats': True,
            'user_agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3',
            'no_warnings': True,
            'quiet': True,
            'outtmpl': init,
            'no_merge': True,
            'test': True,
        }
    
        with yt_dlp.YoutubeDL(ydl_opts) as ydl:
            info_dict = ydl.extract_info(url, download=True)
            url = info_dict.get("url", None)
            if url is None:
                raise ValueError("Failed to download the video")
            video_file_name = ydl.prepare_filename(info_dict)

        pssh = read_pssh(init)

        for file_name in files_to_delete:
            if os.path.exists(file_name):
                os.remove(file_name)
        return pssh

    def get_tracks(self, title):
        player_data = self.content["data"]["page"]["components"][0]["mainAction"]["playerData"]["playable"]
        
        videos = []
        audios = []
        for cr in player_data["videoAssets"]["dash"].values():
            if not cr:
                continue
            for manifest in cr:
                tracks = Tracks.from_mpd(
                    url=manifest["url"],
                    source=self.ALIASES[0],
                    session=self.session,
                )

                for video in tracks.videos:
                    pssh = self.get_pssh_init(manifest["url"])
                    video_pssh = Box.parse(base64.b64decode(pssh))
                    video.pssh = video_pssh
                    video.license_url = manifest["widevineLaUrl"]
                    video.contentId = URL(video.license_url).params._dict["ContentId"][
                        0
                    ]
                    videos += [video]
                # Extract Atmos audio track if available.
                for audio in tracks.audios:
                    audio.pssh = video_pssh
                    audio.license_url = manifest["widevineLaUrl"]
                    audio.contentId = URL(audio.license_url).params._dict["ContentId"][
                        0
                    ]
                    if "atmos" in audio.url:
                        audio.atmos = True
                    audios += [audio]

        corrected_video_list = []
        for res in ("uhd", "hdp", "hd", "sd"):
            for video in videos:
                if f"_{res}_video" not in video.url or not video.url.endswith(
                    f"&r={res}"
                ):
                    continue

                if corrected_video_list and any(
                    video.id == vid.id for vid in corrected_video_list
                ):
                    continue

                if "dash_hevc_hdr" in video.url:
                    video.hdr10 = True
                if "dash_hevc_dolbyvision" in video.url:
                    video.dv = True

                corrected_video_list += [video]

        tracks.add(corrected_video_list)
        tracks.audios = audios
        tracks.videos = [x for x in tracks.videos if (x.codec or "")[:3] in self.VIDEO_CODEC_MAP[self.vcodec]]

        return tracks

    def get_chapters(self, title):
        return []

    def certificate(self, **_):
        return None  # will use common privacy cert

    def license(self, challenge: bytes, track: Tracks, **_) -> bytes:
        license_message = self.session.post(
            url=track.license_url,
            data=challenge,  # expects bytes
        )

        if "errorCode" in license_message.text:
            self.log.exit(f" - Cannot complete license request: {license_message.text}")

        return license_message.content

        
    def configure(self):
        access_token = None
        install_id = None
        for cookie in self.cookies:
            if cookie.name == "secure_access_token":
                access_token = cookie.value
            elif cookie.name == "install_id":
                install_id = cookie.value

        self.access_token = access_token
        self.install_id = install_id

        self.session.headers.update(
            {
                "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36",
                "Origin": "https://moviesanywhere.com",
                "Authorization": f"Bearer {self.access_token}",
            }
        )