# -*- coding: utf-8 -*-
from __future__ import division, absolute_import, print_function, unicode_literals

#################################################################################################

import os
from uuid import uuid4

import requests
import xbmc
import xbmcvfs

from .. import client
from .utils import translate_path

from . import translate, settings, window, dialog, api, LazyLogger

#################################################################################################

LOG = LazyLogger(__name__)


class Transcode(object):
    Enabled = 0
    Audio = 1
    Subtitle = 2
    Disabled = 3
    MediaDefault = 4


#################################################################################################


def set_properties(item, method, server_id=None):
    """Set all properties for playback detection."""
    info = item.get("PlaybackInfo") or {}

    current = window("jellyfin_play.json") or []
    current.append(
        {
            "Type": item["Type"],
            "Id": item["Id"],
            "Path": info["Path"],
            "PlayMethod": method,
            "PlayOption": "Addon" if info.get("PlaySessionId") else "Native",
            "MediaSourceId": info.get("MediaSourceId", item["Id"]),
            "Runtime": item.get("RunTimeTicks"),
            "PlaySessionId": info.get("PlaySessionId", str(uuid4()).replace("-", "")),
            "ServerId": server_id,
            "DeviceId": client.get_device_id(),
            "SubsMapping": info.get("Subtitles"),
            "AudioStreamIndex": info.get("AudioStreamIndex"),
            "SubtitleStreamIndex": info.get("SubtitleStreamIndex"),
            "CurrentPosition": info.get("CurrentPosition"),
            "CurrentEpisode": info.get("CurrentEpisode"),
        }
    )

    window("jellyfin_play.json", current)


class PlayUtils(object):

    def __init__(
        self, item, force_transcode=False, server_id=None, server=None, api_client=None
    ):
        """Item will be updated with the property PlaybackInfo, which
        holds all the playback information.
        """
        self.item = item
        self.item["PlaybackInfo"] = {}
        self.api_client = api_client
        self.info = {
            "ServerId": server_id,
            "ServerAddress": server,
            "ForceTranscode": force_transcode,
            "Token": api_client.config.data["auth.token"],
        }

    def get_sources(self, source_id=None):
        """Return sources based on the optional source_id or the device profile."""
        info = self.api_client.get_play_info(self.item["Id"], self.get_device_profile())
        LOG.info(info)
        self.info["PlaySessionId"] = info["PlaySessionId"]
        sources = []

        if not info.get("MediaSources"):
            LOG.info("No MediaSources found.")

        elif source_id:
            for source in info:

                if source["Id"] == source_id:
                    sources.append(source)

                    break

        elif not self.is_selection(info) or len(info["MediaSources"]) == 1:

            LOG.info("Skip source selection.")
            sources.append(info["MediaSources"][0])

        else:
            sources.extend([x for x in info["MediaSources"]])

        return sources

    def select_source(self, sources, audio=None, subtitle=None):

        if len(sources) > 1:
            selection = []

            for source in sources:
                selection.append(source.get("Name", "na"))

            resp = dialog("select", translate(33130), selection)

            if resp > -1:
                source = sources[resp]
            else:
                LOG.info("No media source selected.")
                return False
        else:
            source = sources[0]

        self.get(source, audio, subtitle)

        return source

    def is_selection(self, sources):
        """Do not allow source selection for."""
        if self.item["MediaType"] != "Video":
            LOG.debug("MediaType is not a video.")

            return False

        elif self.item["Type"] == "TvChannel":
            LOG.debug("TvChannel detected.")

            return False

        elif len(sources) == 1 and sources[0]["Type"] == "Placeholder":
            LOG.debug("Placeholder detected.")

            return False

        elif "SourceType" in self.item and self.item["SourceType"] != "Library":
            LOG.debug("SourceType not from library.")

            return False

        return True

    def is_file_exists(self, source):

        self.direct_play(source)

        if xbmcvfs.exists(self.info["Path"]):
            LOG.info("Path exists.")

            return True

        LOG.info("Failed to find file.")

        return False

    def is_strm(self, source):

        if source.get("Container") == "strm" or self.item.get("Path", "").endswith(
            ".strm"
        ):
            LOG.info("strm detected")

            return True

        return False

    def get(self, source, audio=None, subtitle=None):
        """The server returns sources based on the MaxStreamingBitrate value and other filters.
        prop: jellyfinfilename for ?? I thought it was to pass the real path to subtitle add-ons but it's not working?
        """
        self.info["MediaSourceId"] = source["Id"]

        if source.get("RequiresClosing"):

            """Server returning live tv stream for direct play is hardcoded with 127.0.0.1."""
            self.info["LiveStreamId"] = source["LiveStreamId"]
            source["SupportsDirectPlay"] = False
            source["Protocol"] = "LiveTV"

        if self.info["ForceTranscode"]:

            source["SupportsDirectPlay"] = False
            source["SupportsDirectStream"] = False

        if (
            source.get("Protocol") == "Http"
            or source["SupportsDirectPlay"]
            and (not settings("playFromStream.bool") and self.is_file_exists(source))
        ):

            LOG.info("--[ direct play ]")
            self.direct_play(source)

        elif (
            self.is_strm(source)
            or source["SupportsDirectStream"]
            or source["SupportsDirectPlay"]
        ):

            LOG.info("--[ direct stream ]")
            self.direct_url(source)

        else:
            LOG.info("--[ transcode ]")
            self.transcode(source, audio, subtitle)

        self.info["AudioStreamIndex"] = self.info.get("AudioStreamIndex") or source.get(
            "DefaultAudioStreamIndex"
        )
        self.info["SubtitleStreamIndex"] = self.info.get(
            "SubtitleStreamIndex"
        ) or source.get("DefaultSubtitleStreamIndex")
        self.item["PlaybackInfo"].update(self.info)

        API = api.API(self.item, self.info["ServerAddress"])
        window("jellyfinfilename", value=API.get_file_path(source.get("Path")))

    def live_stream(self, source):
        """Get live stream media info."""
        info = self.api_client.get_live_stream(
            self.item["Id"],
            self.info["PlaySessionId"],
            source["OpenToken"],
            self.get_device_profile(),
        )
        LOG.info(info)

        if info["MediaSource"].get("RequiresClosing"):
            self.info["LiveStreamId"] = source["LiveStreamId"]

        return info["MediaSource"]

    def transcode(self, source, audio=None, subtitle=None):

        if "TranscodingUrl" not in source:
            raise Exception("use get_sources to get transcoding url")

        self.info["Method"] = "Transcode"

        if self.item["MediaType"] == "Video":
            base, params = source["TranscodingUrl"].split("?")
            url_parsed = params.split("&")
            manual_tracks = ""

            # manual bitrate
            url_parsed = [
                p
                for p in url_parsed
                if "AudioBitrate" not in p and "VideoBitrate" not in p
            ]

            if settings("skipDialogTranscode") != Transcode.Enabled and source.get(
                "MediaStreams"
            ):
                # manual tracks
                url_parsed = [
                    p
                    for p in url_parsed
                    if "AudioStreamIndex" not in p and "SubtitleStreamIndex" not in p
                ]
                manual_tracks = self.get_audio_subs(source, audio, subtitle)

            audio_bitrate = self.get_transcoding_audio_bitrate()
            video_bitrate = self.get_max_bitrate() - audio_bitrate

            params = "%s%s" % ("&".join(url_parsed), manual_tracks)
            params += "&VideoBitrate=%s&AudioBitrate=%s" % (
                video_bitrate,
                audio_bitrate,
            )

            # Use mp4 container sparingly due to ffmpeg 6.0.1 seeking bug #7359
            if settings("videoPreferredCodec") == "AV1":
                params += "&SegmentContainer=mp4"

            video_type = "live" if source["Protocol"] == "LiveTV" else "master"
            base = base.replace(
                "stream" if "stream" in base else "master", video_type, 1
            )
            self.info["Path"] = "%s%s?%s" % (self.info["ServerAddress"], base, params)
            self.info["Path"] += "&maxWidth=%s&maxHeight=%s" % (self.get_resolution())
        else:
            self.info["Path"] = "%s/%s" % (
                self.info["ServerAddress"],
                source["TranscodingUrl"],
            )

        return self.info["Path"]

    def direct_play(self, source):

        API = api.API(self.item, self.info["ServerAddress"])
        self.info["Method"] = "DirectPlay"
        self.info["Path"] = API.get_file_path(source.get("Path"))

        return self.info["Path"]

    def direct_url(self, source):

        self.info["Method"] = "DirectStream"

        if self.item["Type"] == "Audio":
            self.info["Path"] = "%s/Audio/%s/stream.%s?static=true&ApiKey=%s" % (
                self.info["ServerAddress"],
                self.item["Id"],
                source.get("Container", "mp4").split(",")[0],
                self.info["Token"],
            )
        else:
            self.info["Path"] = (
                "%s/Videos/%s/stream?static=true&MediaSourceId=%s&ApiKey=%s"
                % (
                    self.info["ServerAddress"],
                    self.item["Id"],
                    source["Id"],
                    self.info["Token"],
                )
            )

        return self.info["Path"]

    def get_max_bitrate(self):
        """Get the video quality based on add-on settings.
        Max bit rate supported by server: 2147483 (max signed 32bit integer)
        """
        bitrate = [
            500,
            1000,
            1500,
            2000,
            2500,
            3000,
            4000,
            5000,
            6000,
            7000,
            8000,
            9000,
            10000,
            12000,
            14000,
            16000,
            18000,
            20000,
            25000,
            30000,
            35000,
            40000,
            100000,
            1000000,
            2147483,
        ]
        return bitrate[int(settings("maxBitrate") or 24)] * 1000

    def get_resolution(self):
        return int(xbmc.getInfoLabel("System.ScreenWidth")), int(
            xbmc.getInfoLabel("System.ScreenHeight")
        )

    def get_directplay_video_codec(self):
        codecs = ["h264", "hevc", "mpeg4", "mpeg2video", "vc1", "vp9", "av1"]

        if settings("transcode_h265.bool"):
            codecs.remove("hevc")

        if settings("transcode_mpeg2.bool"):
            codecs.remove("mpeg2video")

        if settings("transcode_vc1.bool"):
            codecs.remove("vc1")

        if settings("transcode_vp9.bool"):
            codecs.remove("vp9")

        if settings("transcode_av1.bool"):
            codecs.remove("av1")

        return ",".join(codecs)

    def get_transcoding_video_codec(self):
        codecs = ["h264", "mpeg4", "mpeg2video", "vc1"]

        if settings("videoPreferredCodec") == "H265/HEVC":
            codecs.insert(0, "hevc")
        elif not settings("transcode_h265.bool"):
            codecs.insert(1, "hevc")

        if settings("videoPreferredCodec") == "AV1":
            codecs.insert(0, "av1")
        elif not settings("transcode_av1.bool"):
            codecs.append("av1")

        if settings("transcode_mpeg2.bool"):
            codecs.remove("mpeg2video")

        if settings("transcode_vc1.bool"):
            codecs.remove("vc1")

        return ",".join(codecs)

    def get_transcoding_audio_codec(self):
        codecs = ["aac", "mp3", "ac3", "opus", "flac", "vorbis"]

        preferred = settings("audioPreferredCodec").lower()
        if preferred in codecs:
            codecs.insert(0, codecs.pop(codecs.index(preferred)))

        return ",".join(codecs)

    def get_transcoding_audio_bitrate(self):
        bitrate = [96, 128, 160, 192, 256, 320, 384]
        return bitrate[int(settings("audioBitrate") or 6)] * 1000

    def get_device_profile(self):
        """Get device profile based on the add-on settings."""
        profile = {
            "Name": "Kodi",
            "MaxStaticBitrate": self.get_max_bitrate(),
            "MaxStreamingBitrate": self.get_max_bitrate(),
            "MusicStreamingTranscodingBitrate": 1280000,
            "TimelineOffsetSeconds": 5,
            "TranscodingProfiles": [
                {
                    "Type": "Video",
                    "Container": "m3u8",
                    "AudioCodec": self.get_transcoding_audio_codec(),
                    "VideoCodec": self.get_transcoding_video_codec(),
                    "MaxAudioChannels": settings("audioMaxChannels"),
                },
                {"Type": "Audio"},
                {"Type": "Photo", "Container": "jpeg"},
            ],
            "DirectPlayProfiles": [
                {"Type": "Video", "VideoCodec": self.get_directplay_video_codec()},
                {"Type": "Audio"},
                {"Type": "Photo"},
            ],
            "ResponseProfiles": [],
            "ContainerProfiles": [],
            "CodecProfiles": [],
            "SubtitleProfiles": [
                {"Format": "srt", "Method": "External"},
                {"Format": "srt", "Method": "Embed"},
                {"Format": "ass", "Method": "External"},
                {"Format": "ass", "Method": "Embed"},
                {"Format": "sub", "Method": "Embed"},
                {"Format": "sub", "Method": "External"},
                {"Format": "ssa", "Method": "Embed"},
                {"Format": "ssa", "Method": "External"},
                {"Format": "smi", "Method": "Embed"},
                {"Format": "smi", "Method": "External"},
                {"Format": "pgssub", "Method": "Embed"},
                {"Format": "pgssub", "Method": "External"},
                {"Format": "dvdsub", "Method": "Embed"},
                {"Format": "dvdsub", "Method": "External"},
                {"Format": "pgs", "Method": "Embed"},
                {"Format": "pgs", "Method": "External"},
            ],
        }

        if settings("transcodeHi10P.bool"):
            profile["CodecProfiles"].append(
                {
                    "Type": "Video",
                    "codec": "h264",
                    "Conditions": [
                        {
                            "Condition": "LessThanEqual",
                            "Property": "VideoBitDepth",
                            "Value": "8",
                        }
                    ],
                }
            )

        if settings("transcode_h265_rext.bool"):
            profile["CodecProfiles"].append(
                {
                    "Type": "Video",
                    "codec": "hevc",
                    "Conditions": [
                        {
                            "Condition": "EqualsAny",
                            "Property": "VideoProfile",
                            "Value": "main|main 10",
                        }
                    ],
                }
            )

        if self.info["ForceTranscode"]:
            profile["DirectPlayProfiles"] = []

        if self.item["Type"] == "TvChannel":
            profile["TranscodingProfiles"].insert(
                0,
                {
                    "Container": "ts",
                    "Type": "Video",
                    "AudioCodec": "mp3,aac",
                    "VideoCodec": "h264",
                    "Context": "Streaming",
                    "Protocol": "hls",
                    "MaxAudioChannels": "2",
                    "MinSegments": "1",
                    "BreakOnNonKeyFrames": True,
                },
            )

        return profile

    def set_external_subs(self, source, listitem):
        """Try to download external subs locally, so we can label them.
        Since Jellyfin returns all possible tracks together, sort them.
        IsTextSubtitleStream if true, is available to download from server.
        """
        if not settings("enableExternalSubs.bool") or not source["MediaStreams"]:
            return

        subs = []
        mapping = {}
        kodi = 0

        server_settings = self.api_client.get_transcode_settings()

        for stream in source["MediaStreams"]:
            if (
                stream["SupportsExternalStream"]
                and stream["Type"] == "Subtitle"
                and stream["DeliveryMethod"] == "External"
            ):
                if (
                    not stream["IsExternal"]
                    and not server_settings["EnableSubtitleExtraction"]
                ):
                    continue

                index = stream["Index"]
                url = self.get_subtitles(source, stream, index)

                if url is None:
                    continue

                LOG.info("[ subtitles/%s ] %s", index, url)

                if "Language" in stream:
                    filename = "%s.%s.%s" % (
                        source["Id"],
                        stream["Language"],
                        stream["Codec"],
                    )

                    try:
                        subs.append(self.download_external_subs(url, filename))
                    except Exception as error:
                        LOG.exception(error)
                        subs.append(url)
                else:
                    subs.append(url)

                mapping[kodi] = index
                kodi += 1

        listitem.setSubtitles(subs)
        self.item["PlaybackInfo"]["Subtitles"] = mapping

    @classmethod
    def download_external_subs(cls, src, filename):
        """Download external subtitles to temp folder
        to be able to have proper names to streams.
        """
        temp = translate_path(
            "special://profile/addon_data/plugin.video.jellyfin/temp/"
        )

        if not xbmcvfs.exists(temp):
            xbmcvfs.mkdir(temp)

        path = os.path.join(temp, filename)

        try:
            response = requests.get(src, stream=True, verify=settings("sslverify.bool"))
            response.raise_for_status()
        except Exception as error:
            LOG.exception(error)
            raise
        else:
            response.encoding = "utf-8"
            with open(path, "wb") as f:
                f.write(response.content)
                del response

        return path

    def get_audio_subs(self, source, audio=None, subtitle=None):
        """For transcoding only
        Present the list of audio/subs to select from, before playback starts.

        Since Jellyfin returns all possible tracks together, sort them.
        IsTextSubtitleStream if true, is available to download from server.
        """
        prefs = ""
        audio_stream_indexes = list()
        subs_stream_indexes = list()
        streams = dict()

        server_settings = self.api_client.get_transcode_settings()
        allow_burned_subs = settings("allowBurnedSubs.bool")

        for stream in source["MediaStreams"]:
            index = stream["Index"]
            stream_type = stream["Type"]

            streams[index] = stream

            if stream_type == "Audio":
                audio_stream_indexes.append(index)

            elif stream_type == "Subtitle":
                if stream["IsExternal"]:
                    if not stream["SupportsExternalStream"] and not allow_burned_subs:
                        continue
                else:
                    avail_for_extraction = (
                        stream["SupportsExternalStream"]
                        and server_settings["EnableSubtitleExtraction"]
                    )
                    if not avail_for_extraction and not allow_burned_subs:
                        continue

                subs_stream_indexes.append(index)

        skip_dialog = int(settings("skipDialogTranscode") or 0)

        def get_track_title(track_index):
            return streams[track_index]["DisplayTitle"] or ("Track %s" % track_index)

        # Select audio stream
        audio_selected_index = None

        if skip_dialog == Transcode.MediaDefault:
            # NOTE: "DefaultAudioStreamIndex" is the default according to Jellyfin.
            #       The media's default is marked by the "IsDefault" value.
            for track_index in audio_stream_indexes:
                if streams[track_index]["IsDefault"]:
                    audio = track_index
                    break

        # Compare to None in the off-chance the track index is 0.
        if audio is not None:
            audio_selected_index = audio

        elif skip_dialog in (Transcode.Enabled, Transcode.Audio):
            if len(audio_stream_indexes) > 1:
                selection = list(map(get_track_title, audio_stream_indexes))
                resp = dialog("select", translate(33013), selection)
                if resp > -1:
                    audio_selected_index = audio_stream_indexes[resp]
                else:
                    audio_selected_index = source["DefaultAudioStreamIndex"]
            elif audio_stream_indexes:
                # Only one choice
                audio_selected_index = audio_stream_indexes[0]

        else:
            audio_selected_index = source["DefaultAudioStreamIndex"]

        if audio_selected_index is not None:
            self.info["AudioStreamIndex"] = audio_selected_index
            prefs += "&AudioStreamIndex=%s" % audio_selected_index

        # Select audio stream
        subtitle_selected_index = None

        if skip_dialog == Transcode.MediaDefault:
            for track_index in subs_stream_indexes:
                if streams[track_index]["IsDefault"]:
                    subtitle = track_index
                    break

        if subtitle is not None:
            subtitle_selected_index = subtitle

        elif (
            skip_dialog in (Transcode.Enabled, Transcode.Subtitle)
            and subs_stream_indexes
        ):
            selection = list(["No subtitles"]) + list(
                map(get_track_title, subs_stream_indexes)
            )
            resp = dialog("select", translate(33014), selection) - 1
            # Possible responses:
            # >=0  Subtitle track
            #  -1  No subtitles (Default)
            #  -2  Dialog was cancelled
            if resp > -1:
                subtitle_selected_index = subs_stream_indexes[resp]

        if subtitle_selected_index is not None:
            server_settings = self.api_client.get_transcode_settings()
            stream = streams[subtitle_selected_index]

            if stream["SupportsExternalStream"] and (
                stream["IsExternal"] or server_settings["EnableSubtitleExtraction"]
            ):
                self.info["SubtitleUrl"] = self.get_subtitles(
                    source, stream, subtitle_selected_index
                )
                self.info["SubtitleStreamIndex"] = subtitle_selected_index
            elif allow_burned_subs:
                prefs += "&SubtitleStreamIndex=%s" % subtitle_selected_index
                self.info["SubtitleStreamIndex"] = subtitle_selected_index

                prefs += "&SubtitleMethod=Encode"
                self.info["SubtitleMethod"] = "Encode"

        return prefs

    def get_subtitles(self, source, stream, index):

        if (
            stream["IsTextSubtitleStream"]
            and "DeliveryUrl" in stream
            and stream["DeliveryUrl"].lower().startswith("/videos")
        ):
            url = "%s%s" % (self.info["ServerAddress"], stream["DeliveryUrl"])
        else:
            url = "%s/Videos/%s/%s/Subtitles/%s/Stream.%s?ApiKey=%s" % (
                self.info["ServerAddress"],
                self.item["Id"],
                source["Id"],
                index,
                stream["Codec"],
                self.info["Token"],
            )

        return url
