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

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

import os

import xbmc
import xbmcvfs

from .objects.obj import Objects
from .helper import translate, api, window, settings, dialog, event, JSONRPC
from .jellyfin import Jellyfin
from .helper import LazyLogger
from .helper.utils import translate_path
from .segments import SegmentChecker

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

LOG = LazyLogger(__name__)

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


class Player(xbmc.Player):

    played = {}
    up_next = False
    skip_segments = {}
    skip_prompted = set()
    skip_dialog = None
    segment_checker = None

    def __init__(self):
        xbmc.Player.__init__(self)

    def get_playing_file(self):
        try:
            return self.getPlayingFile()
        except Exception as error:
            LOG.exception(error)

    def get_file_info(self, file):
        try:
            return self.played[file]
        except Exception as error:
            LOG.exception(error)

    def is_playing_file(self, file):
        return file in self.played

    def onPlayBackStarted(self):
        """We may need to wait for info to be set in kodi monitor.
        Accounts for scenario where Kodi starts playback and exits immediately.
        First, ensure previous playback terminated correctly in Jellyfin.
        """

        self.stop_playback()
        self._reset_state(restart=True)
        count = 0
        monitor = xbmc.Monitor()

        try:
            current_file = self.getPlayingFile()

        except Exception:

            while count < 5:
                try:
                    current_file = self.getPlayingFile()
                    count = 0
                    break
                except Exception:
                    count += 1

                if monitor.waitForAbort(1):
                    return
            else:
                LOG.info("Cancel playback report")

                return

        items = window("jellyfin_play.json")
        item = None

        while not items:

            if monitor.waitForAbort(2):
                return

            items = window("jellyfin_play.json")
            count += 1

            if count == 20:
                LOG.info("Could not find jellyfin prop...")

                return

        for item in items:
            if item["Path"] == current_file:
                items.pop(items.index(item))

                break
        else:
            item = items.pop(0)

        window("jellyfin_play.json", items)

        self.set_item(current_file, item)
        # Detect current audio/subtitle state from Kodi player
        self.detect_audio_subs(item)
        data = {
            "QueueableMediaTypes": "Video,Audio",
            "CanSeek": True,
            "ItemId": item["Id"],
            "MediaSourceId": item["MediaSourceId"],
            "PlayMethod": item["PlayMethod"],
            "VolumeLevel": item["Volume"],
            "PositionTicks": int(item["CurrentPosition"] * 10000000),
            "IsPaused": item["Paused"],
            "IsMuted": item["Muted"],
            "PlaySessionId": item["PlaySessionId"],
            "AudioStreamIndex": item["AudioStreamIndex"],
            "SubtitleStreamIndex": item["SubtitleStreamIndex"],
        }
        try:
            item["Server"].jellyfin.session_playing(data)
        except Exception as e:
            LOG.warning("Failed to report session playing: %s", e)
        window("jellyfin.skip.%s.bool" % item["Id"], True)

        # Immediate skip check for segments starting at 0:00
        if settings("mediaSegmentsEnabled.bool"):
            try:
                self._fetch_skip_segments(item)
                current_pos = int(self.getTime())
                self.check_skip_segments(item, current_pos)
            except Exception:
                pass  # Player may not be ready yet

        if monitor.waitForAbort(2):
            return

        if item["PlayOption"] == "Addon":
            self.set_audio_subs(item["AudioStreamIndex"], item["SubtitleStreamIndex"])

    def set_item(self, file, item):
        """Set playback information."""
        try:
            item["Runtime"] = int(item["Runtime"])
        except (TypeError, ValueError):
            try:
                item["Runtime"] = int(self.getTotalTime())
                LOG.info("Runtime is missing, Kodi runtime: %s" % item["Runtime"])
            except Exception:
                item["Runtime"] = 0
                LOG.info("Runtime is missing, Using Zero")

        try:
            self.getTime()  # confirm the player is ready; bail out if it isn't
        except Exception:  # at this point we should be playing and if not then bail out
            return

        result = JSONRPC("Application.GetProperties").execute(
            {"properties": ["volume", "muted"]}
        )
        result = result.get("result", {})
        volume = result.get("volume")
        muted = result.get("muted")

        item.update(
            {
                "File": file,
                # A fresh playback start is at 0 (or the resume point, which is
                # already in item["CurrentPosition"]). Do NOT fall back to the live
                # player clock here: on a track change getTime() still returns the
                # previous track's position, which would report the new track as
                # starting mid-way and stop clients resetting their progress bar.
                "CurrentPosition": item.get("CurrentPosition") or 0,
                "Muted": muted,
                "Volume": volume,
                "Server": Jellyfin(item["ServerId"]).get_client(),
                "Paused": False,
            }
        )

        self.played[file] = item
        window("jellyfin_playing_id", item["Id"])
        LOG.info("-->[ play/%s ] %s", item["Id"], item)

    def set_audio_subs(self, audio=None, subtitle=None):
        if audio:
            audio = int(audio)
        if subtitle:
            subtitle = int(subtitle)

        """ Only for after playback started
        """
        LOG.info("Setting audio: %s subs: %s", audio, subtitle)
        current_file = self.get_playing_file()

        if self.is_playing_file(current_file):

            item = self.get_file_info(current_file)
            mapping = item["SubsMapping"]

            if audio and len(self.getAvailableAudioStreams()) > 1:
                self.setAudioStream(audio - 1)

            if subtitle is None or subtitle == -1:
                self.showSubtitles(False)

                return

            tracks = len(self.getAvailableAudioStreams())

            if mapping:
                for index in mapping:

                    if mapping[index] == subtitle:
                        self.setSubtitleStream(int(index))

                        break
                else:
                    self.setSubtitleStream(len(mapping) + subtitle - tracks - 1)
            else:
                self.setSubtitleStream(subtitle - tracks - 1)

    def detect_audio_subs(self, item):

        params = {
            "playerid": 1,
            "properties": ["currentsubtitle", "currentaudiostream", "subtitleenabled"],
        }
        result = JSONRPC("Player.GetProperties").execute(params)
        result = result.get("result")

        try:  # Audio tracks
            audio = result["currentaudiostream"]["index"]
        except (KeyError, TypeError):
            audio = 0

        try:  # Subtitles tracks
            subs = result["currentsubtitle"]["index"]
        except (KeyError, TypeError):
            subs = 0

        try:  # If subtitles are enabled
            subs_enabled = result["subtitleenabled"]
        except (KeyError, TypeError):
            subs_enabled = False

        item["AudioStreamIndex"] = audio + 1

        if not subs_enabled or not len(self.getAvailableSubtitleStreams()):
            item["SubtitleStreamIndex"] = None

            return

        mapping = item["SubsMapping"]
        tracks = len(self.getAvailableAudioStreams())

        if mapping:
            if str(subs) in mapping:
                item["SubtitleStreamIndex"] = mapping[str(subs)]
            else:
                item["SubtitleStreamIndex"] = subs - len(mapping) + tracks + 1
        else:
            item["SubtitleStreamIndex"] = subs + tracks + 1

    def next_up(self):

        item = self.get_file_info(self.get_playing_file())
        objects = Objects()

        if item["Type"] != "Episode" or not item.get("CurrentEpisode"):
            return

        next_items = item["Server"].jellyfin.get_adjacent_episodes(
            item["CurrentEpisode"]["tvshowid"], item["Id"]
        )

        for index, next_item in enumerate(next_items["Items"]):
            if next_item["Id"] == item["Id"]:

                try:
                    next_item = next_items["Items"][index + 1]
                except IndexError:
                    LOG.warning("No next up episode.")

                    return

                break
        server_address = item["Server"].auth.get_server_info(
            item["Server"].auth.server_id
        )["address"]
        API = api.API(next_item, server_address)
        data = objects.map(next_item, "UpNext")
        artwork = API.get_all_artwork(objects.map(next_item, "ArtworkParent"), True)
        data["art"] = {
            "tvshow.poster": artwork.get("Series.Primary"),
            "tvshow.fanart": None,
            "thumb": artwork.get("Primary"),
        }
        if artwork["Backdrop"]:
            data["art"]["tvshow.fanart"] = artwork["Backdrop"][0]

        next_info = {
            "play_info": {
                "ItemIds": [data["episodeid"]],
                "ServerId": item["ServerId"],
                "PlayCommand": "PlayNow",
            },
            "current_episode": item["CurrentEpisode"],
            "next_episode": data,
        }

        LOG.info("--[ next up ] %s", next_info)
        event("upnext_data", next_info, hexlify=True)

    def onPlayBackPaused(self):
        current_file = self.get_playing_file()

        if self.is_playing_file(current_file):

            self.get_file_info(current_file)["Paused"] = True
            self.report_playback()
            LOG.debug("-->[ paused ]")

    def onPlayBackResumed(self):
        current_file = self.get_playing_file()

        if self.is_playing_file(current_file):

            self.get_file_info(current_file)["Paused"] = False
            self.report_playback()
            LOG.debug("--<[ paused ]")

    def onPlayBackSeek(self, time, seek_offset):
        """Does not seem to work in Leia??"""
        if self.is_playing_file(self.get_playing_file()):

            self.report_playback()
            LOG.info("--[ seek ]")

            # Check skip segments immediately after seek
            if settings("mediaSegmentsEnabled.bool"):
                try:
                    current_file = self.get_playing_file()
                    item = self.get_file_info(current_file)
                    current_pos = int(self.getTime())
                    self.check_skip_segments(item, current_pos)
                except Exception:
                    pass

    def report_playback(self, report=True):
        """Report playback progress to jellyfin server.
        Check if the user seek.
        """
        current_file = self.get_playing_file()

        if not self.is_playing_file(current_file):
            return

        item = self.get_file_info(current_file)

        if window("jellyfin.external.bool"):
            return

        if not report:
            previous = item["CurrentPosition"]

            try:
                item["CurrentPosition"] = int(self.getTime())
            except Exception as e:
                # getTime() raises RuntimeError if nothing is playing
                LOG.debug("Failed to get playback position: %s", e)
                return

            if int(item["CurrentPosition"]) == 1:
                return

            try:
                played = (
                    float(item["CurrentPosition"] * 10000000)
                    / int(item["Runtime"])
                    * 100
                )
            except ZeroDivisionError:  # Runtime is 0.
                played = 0

            if played > 2.0 and not self.up_next:

                self.up_next = True
                self.next_up()

            if (item["CurrentPosition"] - previous) < 30:
                return

        result = JSONRPC("Application.GetProperties").execute(
            {"properties": ["volume", "muted"]}
        )
        result = result.get("result", {})
        item["Volume"] = result.get("volume")
        item["Muted"] = result.get("muted")
        item["CurrentPosition"] = int(self.getTime())
        self.detect_audio_subs(item)

        data = {
            "QueueableMediaTypes": "Video,Audio",
            "CanSeek": True,
            "ItemId": item["Id"],
            "MediaSourceId": item["MediaSourceId"],
            "PlayMethod": item["PlayMethod"],
            "VolumeLevel": item["Volume"],
            "PositionTicks": int(item["CurrentPosition"] * 10000000),
            "IsPaused": item["Paused"],
            "IsMuted": item["Muted"],
            "PlaySessionId": item["PlaySessionId"],
            "AudioStreamIndex": item["AudioStreamIndex"],
            "SubtitleStreamIndex": item["SubtitleStreamIndex"],
        }
        item["Server"].jellyfin.session_progress(data)

    def onPlayBackStopped(self):
        """Will be called when user stops playing a file."""
        window("jellyfin_play", clear=True)
        self.stop_playback()
        LOG.info("--<[ playback ]")

    def onPlayBackEnded(self):
        """Will be called when kodi stops playing a file."""
        self.stop_playback()
        LOG.info("--<<[ playback ]")

    def stop_playback(self):
        """Stop all playback. Check for external player for positionticks."""
        if not self.played:
            return

        self._reset_state()

        LOG.info("Played info: %s", self.played)

        for file in self.played:
            item = self.get_file_info(file)

            window("jellyfin.skip.%s.bool" % item["Id"], True)

            if window("jellyfin.external.bool"):
                window("jellyfin.external", clear=True)

                if int(item["CurrentPosition"]) == 1:
                    item["CurrentPosition"] = int(item["Runtime"])

            data = {
                "ItemId": item["Id"],
                "MediaSourceId": item["MediaSourceId"],
                "PositionTicks": int(item["CurrentPosition"] * 10000000),
                "PlaySessionId": item["PlaySessionId"],
            }
            item["Server"].jellyfin.session_stop(data)

            if item.get("LiveStreamId"):

                LOG.info("<[ livestream/%s ]", item["LiveStreamId"])
                item["Server"].jellyfin.close_live_stream(item["LiveStreamId"])

            elif item["PlayMethod"] == "Transcode":

                LOG.info("<[ transcode/%s ]", item["Id"])
                item["Server"].jellyfin.close_transcode(
                    item["DeviceId"], item["PlaySessionId"]
                )

            path = translate_path(
                "special://profile/addon_data/plugin.video.jellyfin/temp/"
            )

            if xbmcvfs.exists(path):
                dirs, files = xbmcvfs.listdir(path)

                for file in files:
                    # Only delete the cached files for the previous play session
                    if item["Id"] in file:
                        xbmcvfs.delete(os.path.join(path, file))

            result = item["Server"].jellyfin.get_item(item["Id"]) or {}

            if "UserData" in result and result["UserData"]["Played"]:
                delete = False

                if result["Type"] == "Episode" and settings("deleteTV.bool"):
                    delete = True
                elif result["Type"] == "Movie" and settings("deleteMovies.bool"):
                    delete = True

                if not settings("offerDelete.bool"):
                    delete = False

                if delete:
                    LOG.info("Offer delete option")

                    if dialog(
                        "yesno", translate(30091), translate(33015), autoclose=120000
                    ):
                        item["Server"].jellyfin.delete_item(item["Id"])

            window("jellyfin.external_check", clear=True)

        self.played.clear()
        window("jellyfin_playing_id", clear=True)

    def _fetch_skip_segments(self, item):
        if not settings("mediaSegmentsEnabled.bool"):
            return

        item_id = item["Id"]
        self.skip_segments.pop(item_id, None)
        self.skip_prompted = set()

        self._reset_skip_dialog()

        segments = item["Server"].jellyfin.get_media_segments(item_id)
        if segments:
            segments = self._convert_media_segments(segments)

        if segments:
            self.skip_segments[item_id] = segments
            LOG.info("Loaded media segments for %s: %s", item_id, list(segments.keys()))

    def _convert_media_segments(self, response):
        if not response or "Items" not in response:
            return None

        type_map = {
            "Intro": "Introduction",
            "Outro": "Credits",
            "Recap": "Recap",
            "Preview": "Preview",
            "Commercial": "Commercial",
        }

        segments = {}
        for item in response["Items"]:
            seg_type = type_map.get(item.get("Type"))
            if seg_type:
                segments[seg_type] = {
                    "EpisodeId": item.get("ItemId"),
                    "Start": item.get("StartTicks", 0) / 10000000.0,
                    "End": item.get("EndTicks", 0) / 10000000.0,
                }
        return segments if segments else None

    def _process_segment(
        self, item_id, segment_type, segment, current_position, skip_mode
    ):
        """Check if current position is within segment bounds. Returns (start, end) tuple, None if outside bounds, or False if invalid."""
        start = segment.get("Start")
        end = segment.get("End")
        if start is None or end is None or end <= start:
            return False

        LOG.debug(
            "Skip check: pos=%.1f, %s start=%.1f end=%.1f, in_segment=%s",
            current_position,
            segment_type,
            start,
            end,
            start <= current_position <= end,
        )

        if not (start <= current_position <= end):
            return None

        return (start, end)

    def check_skip_segments(self, item, current_position):
        item_id = item["Id"]
        segments = self.skip_segments.get(item_id)
        if not segments:
            return

        for segment_type, segment in segments.items():
            skip_mode = self._get_segment_skip_mode(segment_type)
            if skip_mode == 0:  # Off
                continue

            bounds = self._process_segment(
                item_id, segment_type, segment, current_position, skip_mode
            )
            if not bounds:
                continue

            start, end = bounds
            segment_key = "%s:%s" % (item_id, segment_type)
            LOG.debug(
                "Skip check: IN WINDOW! segment_key=%s, already_prompted=%s",
                segment_key,
                segment_key in self.skip_prompted,
            )
            if segment_key in self.skip_prompted:
                continue

            self.skip_prompted.add(segment_key)
            LOG.debug(
                "Skip check: Triggering _handle_skip_segment for %s", segment_type
            )

            if segment_type == "Credits" and not self.up_next:
                self.up_next = True
                self.next_up()

            self._handle_skip_segment(segment_type, start, end, skip_mode)
            break

    def _get_segment_skip_mode(self, segment_type):
        """Get the skip mode for a segment type. Returns 0=Off, 1=Auto, 2=Button."""
        setting_map = {
            "Introduction": "skipIntroductionMode",
            "Credits": "skipCreditsMode",
            "Recap": "skipRecapMode",
            "Preview": "skipPreviewMode",
            "Commercial": "skipCommercialMode",
        }
        setting_key = setting_map.get(segment_type)
        if not setting_key:
            return 0
        return int(settings(setting_key) or 0)

    def _get_runtime_for_seek(self):
        try:
            total_time = float(self.getTotalTime())
            if total_time > 0:
                return total_time
        except Exception:
            pass

        try:
            current_file = self.get_playing_file()
            if current_file and self.is_playing_file(current_file):
                item = self.get_file_info(current_file)
                runtime = float(item.get("Runtime") or 0)
                if runtime > 0:
                    return runtime
        except Exception:
            pass

        return 0.0

    def _get_safe_seek_time(self, seek_time, margin=1.0):
        try:
            safe_seek_time = max(0.0, float(seek_time))
        except (TypeError, ValueError):
            return None

        runtime = self._get_runtime_for_seek()
        if runtime > 0:
            max_seek_time = runtime - margin
            if max_seek_time <= 0:
                max_seek_time = runtime
            safe_seek_time = min(safe_seek_time, max_seek_time)

        try:
            current_position = float(self.getTime())
            if safe_seek_time <= current_position:
                return None
        except Exception:
            pass

        return safe_seek_time

    def _handle_skip_segment(self, segment_type, start, end, mode):
        safe_end = self._get_safe_seek_time(end)
        if safe_end is None or safe_end <= start:
            return

        LOG.debug(
            "_handle_skip_segment: type=%s, mode=%d, start=%.1f, end=%.1f, safe_end=%.1f",
            segment_type,
            mode,
            start,
            end,
            safe_end,
        )

        if mode == 1:  # Auto skip
            self.seekTime(safe_end)
            LOG.info("Auto-skipped %s to %.1f", segment_type, safe_end)
            # Show notification
            message = "Skipped %s" % segment_type
            dialog(
                "notification",
                heading="Jellyfin",
                message=message,
                icon="{jellyfin}",
                time=3000,
            )

        elif mode == 2:  # Show skip button
            self._show_skip_button(segment_type, safe_end - start, safe_end)

    def _show_skip_button(self, segment_type, duration, end_time):
        LOG.debug(
            "_show_skip_button: type=%s, duration=%.1f, end_time=%.1f",
            segment_type,
            duration,
            end_time,
        )
        try:
            import xbmcaddon
            from .dialogs.skip import SkipDialog

            self._reset_skip_dialog()

            addon_path = xbmcaddon.Addon("plugin.video.jellyfin").getAddonInfo("path")
            LOG.debug("_show_skip_button: addon_path=%s", addon_path)

            self.skip_dialog = SkipDialog(
                "script-jellyfin-skip.xml",
                addon_path,
                "default",
                "1080i",
            )
            self.skip_dialog.set_skip_info(segment_type, duration)
            LOG.debug("_show_skip_button: calling show()")
            self.skip_dialog.show()
            LOG.debug("_show_skip_button: show() completed")

            self._skip_end_time = end_time
            self._monitor_skip_dialog()
        except Exception as e:
            LOG.error("_show_skip_button error: %s", e, exc_info=True)

    def _monitor_skip_dialog(self):
        """Monitor the skip dialog and handle user input or timeout."""
        LOG.debug("_monitor_skip_dialog: starting, end_time=%.1f", self._skip_end_time)
        monitor = xbmc.Monitor()

        # Monitor loop - check for user input or end of segment
        while self.skip_dialog and not monitor.abortRequested():
            # Check if user clicked skip
            if self.skip_dialog.is_skip():
                self.seekTime(self._skip_end_time)
                LOG.info("User skipped to %.1f", self._skip_end_time)
                break

            # Check if user cancelled
            if self.skip_dialog.is_cancel():
                LOG.debug("User cancelled skip dialog")
                break

            # Check if we've passed the segment end time
            try:
                current_pos = self.getTime()
                if current_pos >= self._skip_end_time:
                    LOG.debug(
                        "_monitor_skip_dialog: passed end_time %.1f, closing",
                        self._skip_end_time,
                    )
                    break
            except Exception:
                break

            if monitor.waitForAbort(0.2):
                break

        LOG.debug("_monitor_skip_dialog: exiting loop")
        self._reset_skip_dialog()

    def _reset_state(self, restart=False):
        self._reset_segment_checker(restart)
        self._reset_skip_dialog()

        self.up_next = False
        self.skip_segments = {}
        self.skip_prompted = set()

    def _reset_segment_checker(self, restart=False):
        if self.segment_checker:
            self.segment_checker.stop()

        if restart:
            self.segment_checker = SegmentChecker(player=self)
            self.segment_checker.start()

    def _reset_skip_dialog(self):
        if self.skip_dialog:
            try:
                self.skip_dialog.close()
            except Exception:
                pass

        self.skip_dialog = None
