import base64
import os
import sys
import socket
import time

from kodi_six import xbmc, xbmcplugin, xbmcgui
from kodi_six.utils import py2_decode

import six
from six.moves import urllib_request
from six.moves import urllib_parse
from six.moves import urllib_response
from six.moves import urllib_error

from elementum.kodiutils import restart_signal
from elementum.logger import log
from elementum.config import ELEMENTUMD_HOST
from elementum.addon import ADDON, ADDON_ID, ADDON_PATH
from elementum.osarch import PLATFORM
from elementum.unicode import to_unicode
from elementum.util import notify, getLocalizedString, getLocalizedLabel, getLocalizedText

try:
    import simplejson as json
except ImportError:
    import json


HANDLE = int(sys.argv[1])


class RedirectException(Exception):
    pass


class PlayerException(Exception):
    pass


class InfoLabels(dict):
    def __init__(self, *args, **kwargs):
        self.update(*args, **kwargs)

    def __getitem__(self, key):
        return dict.get(self, key.lower(), "")

    def __setitem__(self, key, val):
        dict.__setitem__(self, key.lower(), val)

    def update(self, *args, **kwargs):
        for k, v in dict(*args, **kwargs).items():
            self[k] = v


class closing(object):
    def __init__(self, thing):
        self.thing = thing

    def __enter__(self):
        return self.thing

    def __exit__(self, *exc_info):
        self.thing.close()


class NoRedirectHandler(urllib_request.HTTPRedirectHandler):
    def http_error_302(self, req, fp, code, msg, headers):
        infourl = urllib_response.addinfourl(fp, headers, headers["Location"])
        try:
            infourl.status = code
        except AttributeError:
            pass
        infourl.code = code
        log.debug("Redirecting with code %d to: %s", code, headers["Location"])
        return infourl
    http_error_300 = http_error_302
    http_error_301 = http_error_302
    http_error_303 = http_error_302
    http_error_307 = http_error_302

def delistLabel(labels, key):
    if key in labels:
        labels[key] = ', '.join(labels[key])

def normalizeLabels(labels):
    if PLATFORM['kodi'] <= 16:
        for i, item in enumerate(labels):
            if isinstance(labels[item], tuple) or isinstance(labels[item], list):
                delistLabel(labels, item)

    elif PLATFORM['kodi'] == 17:
        delistLabel(labels, 'genre')
        delistLabel(labels, 'country')
        delistLabel(labels, 'director')
        delistLabel(labels, 'studio')
        delistLabel(labels, 'writer')
        delistLabel(labels, 'tag')
        delistLabel(labels, 'showlink')
        delistLabel(labels, 'credits')

    return labels

def getInfoLabels():
    id_list = [int(s) for s in sys.argv[0].split("/") if s.isdigit()]
    tmdb_id = id_list[0] if id_list else None

    # if xbmc.getInfoLabel("ListItem.DBID"):
    #     url = "%s/infolabels" % (ELEMENTUMD_HOST)
    # elif not tmdb_id:
    if not tmdb_id:
        request_url = sys.argv[0] + sys.argv[2]
        parsed_url = urllib_parse.urlparse(request_url)
        query = urllib_parse.parse_qs(parsed_url.query)
        query['index'] = query['index'][0] if 'index' in query else ''
        log.debug("Parsed URL: %s, Query: %s", repr(parsed_url), repr(query))
        if 'tmdb' in query and 'type' in query and query['type'][0] == 'search':
            tmdb_id = query['tmdb'][0]
            url = "%s/search/infolabels/%s?index=%s" % (ELEMENTUMD_HOST, tmdb_id, query['index'])
        elif '/search' in parsed_url and 'tmdb' in query:
            tmdb_id = query['tmdb'][0]
            url = "%s/search/infolabels/%s?index=%s" % (ELEMENTUMD_HOST, tmdb_id, query['index'])
        elif '/search' in parsed_url and 'q' in query:
            tmdb_id = py2_decode(query['q'][0])
            url = "%s/search/infolabels/%s?index=%s" % (ELEMENTUMD_HOST, tmdb_id, query['index'])
        elif 'tmdb' in query and 'type' in query and query['type'][0] == 'movie':
            tmdb_id = query['tmdb'][0]
            url = "%s/movie/%s/infolabels" % (ELEMENTUMD_HOST, tmdb_id)
        elif 'show' in query:
            tmdb_id = query['show'][0]
            if 'season' in query and 'episode' in query:
                url = "%s/show/%s/season/%s/episode/%s/infolabels" % (ELEMENTUMD_HOST, tmdb_id, query['season'][0], query['episode'][0])
            else:
                url = "%s/show/%s/infolabels" % (ELEMENTUMD_HOST, tmdb_id)
        else:
            url = "%s/infolabels" % (ELEMENTUMD_HOST)
    elif 'movie' in sys.argv[0]:
        url = "%s/movie/%s/infolabels" % (ELEMENTUMD_HOST, tmdb_id)
    elif ('episode' in sys.argv[0] or 'show' in sys.argv[0]) and len(id_list) > 2:
        url = "%s/show/%s/season/%s/episode/%s/infolabels" % (ELEMENTUMD_HOST, tmdb_id, id_list[1], id_list[2])
    elif 'show' in sys.argv[0] and len(id_list) == 2:
        url = "%s/show/%s/season/%s/episode/%s/infolabels" % (ELEMENTUMD_HOST, tmdb_id, id_list[1], 1)
    else:
        url = "%s/infolabels" % (ELEMENTUMD_HOST)

    log.debug("Resolving TMDB item by calling %s for %s" % (url, repr(sys.argv)))

    try:
        with closing(urllib_request.urlopen(url)) as response:
            resolved = json.loads(to_unicode(response.read()), parse_int=str)
            if not resolved:
                return {}

            if 'info' in resolved and resolved['info']:
                resolved.update(resolved['info'])

            if 'art' in resolved and resolved['art']:
                resolved['artbanner'] = ''
                for k, v in resolved['art'].items():
                    resolved['art' + k] = v

            if 'info' in resolved:
                del resolved['info']
            if 'properties' in resolved:
                del resolved['properties']
            if 'art' in resolved:
                del resolved['art']
            if 'artavailable_artworks' in resolved:
                del resolved['artavailable_artworks']
            if 'stream_info' in resolved:
                del resolved['stream_info']
            if 'uniqueids' in resolved:
                del resolved['uniqueids']

            if 'DBTYPE' not in resolved:
                resolved['DBTYPE'] = 'video'
            if 'mediatype' not in resolved or resolved['mediatype'] == '':
                resolved['mediatype'] = resolved['DBTYPE']

            return resolved
    except:
        log.debug("Could not resolve TMDB item: %s" % tmdb_id)
        return {}

def remove_dbtype_from_list(list):
    if 'dbtype' in list:
        del list['dbtype']
    if 'DBTYPE' in list:
        del list['DBTYPE']

    return list

def _json(url):
    with closing(urllib_request.urlopen(url)) as response:
        info = response.info()
        if hasattr(info, 'getheader'):
            location = info.getheader('Location')
        else:
            location = info.get('Location')

        if response.code == 300:
            raise PlayerException(location)
        elif response.code == 301:
            raise RedirectException(location)
        elif response.code >= 302 and response.code <= 307:
            # Pause currently playing Elementum file to avoid doubling requests
            try:
                if xbmc.Player().isPlaying() and ADDON_ID in xbmc.Player().getPlayingFile():
                    xbmc.Player().pause()
            except:
                pass

            _infoLabels = InfoLabels(getInfoLabels())
            if 'mediatype' not in _infoLabels or not _infoLabels['mediatype']:
                _infoLabels['mediatype'] = 'episode'

            if PLATFORM['kodi'] >= 19:
                item = xbmcgui.ListItem(
                    path=response.geturl(),
                    label=_infoLabels["label"],
                    label2=_infoLabels["label2"]
                )
            else:
                item = xbmcgui.ListItem(
                    path=response.geturl(),
                    label=_infoLabels["label"],
                    label2=_infoLabels["label2"],
                    thumbnailImage=_infoLabels["thumbnail"]
                )
            if PLATFORM['kodi'] >= 20:
                video_info_tag = item.getVideoInfoTag()

            item.setArt({
                "thumb": _infoLabels["artthumb"],
                "poster": _infoLabels["artposter"],
                "tvshowposter": _infoLabels["arttvshowposter"],
                "banner": _infoLabels["artbanner"],
                "fanart": _infoLabels["artfanart"],
                "clearart": _infoLabels["artclearart"],
                "clearlogo": _infoLabels["artclearlogo"],
                "landscape": _infoLabels["artlandscape"],
                "icon": _infoLabels["articon"]
            })

            if 'castmembers' in _infoLabels:
                if PLATFORM['kodi'] >= 20:
                    actors = []
                    for castmember in _infoLabels['castmembers']:
                        if 'order' in castmember:
                            castmember['order'] = int(castmember['order'])
                        actor = xbmc.Actor(name=castmember.get('name', ''),
                                           role=castmember.get('role', ''),
                                           thumbnail=castmember.get('thumbnail', ''),
                                           order=castmember.get('order', -1))
                        actors.append(actor)
                    video_info_tag.setCast(actors)
                elif PLATFORM['kodi'] >= 17:
                    item.setCast(_infoLabels['castmembers'])
                del _infoLabels['castmembers']

            _infoLabels = normalizeLabels(_infoLabels)
            remove_dbtype_from_list(_infoLabels)

            item.setInfo(type='video', infoLabels=_infoLabels)
            xbmcplugin.setResolvedUrl(HANDLE, True, item)
            return

            # TODO: If somebody wants to try to fix the issue with having correct naming in Kodi player information.
            # Currently Kodi pushes file name, which is stored on the disk (inside of strm file).
            # And the problem is that we can't change it (or at least, I'm not aware of any ways to do it).
            # So I have tried to remove it and create a playlist, but that does not work with starting anything from a library.

            # try:
            #     log.debug("PLAYING: %s" % (xbmc.Player().getPlayingFile()))
            # except:
            #     log.debug("NOT PLAYING")

            # if "elementum" in response.geturl().lower() and "http" not in response.geturl().lower():
            #     log.debug("RESOLVED: %s" % response.geturl().lower())
            #     xbmcplugin.setResolvedUrl(HANDLE, True, item)
            # else:
            #     playlistId = xbmc.PLAYLIST_VIDEO
            #     if xbmc.PlayList(1).size() > 0:
            #         playlistId = 1
            #     elif xbmc.PlayList(0).size() > 0:
            #         playlistId = 0

            #     playlist = xbmc.PlayList(playlistId)
            #     playlist.clear()
            #     playlist.add(url=response.geturl(), listitem=item, index=0)
            #     log.debug("PLAYLIST: %s --- %d" % (response.geturl(), playlist.size()))
            #     xbmc.Player().play(playlist)

            # return

        payload = to_unicode(response.read())

        try:
            if payload:
                return json.loads(payload)
        except:
            raise Exception(payload)


def run(url_suffix="", retry=0):
    if '/restart/' in sys.argv[0]:
        restart_signal()
        return

    try:
        buffer_timeout = int(ADDON.getSetting("buffer_timeout"))
        if buffer_timeout < 60:
            buffer_timeout = 60
    except:
        buffer_timeout = 60
    buffer_timeout = buffer_timeout * 2

    try:
        preload_timeout = int(ADDON.getSetting("preload_timeout"))
        if preload_timeout < 1:
            preload_timeout = 1
    except:
        preload_timeout = 1

    socket.setdefaulttimeout(buffer_timeout)
    opener = urllib_request.build_opener(NoRedirectHandler())
    opener.addheaders = [('User-Agent', ADDON_ID)]

    login = ADDON.getSetting("remote_login")
    password = ADDON.getSetting("remote_password")
    if login or password:
        log.debug("Setting auth to %s:%s" % (login, password))
        base64string = base64.b64encode('{}:{}'.format(login, password).encode())
        opener.addheaders = [
            ('User-Agent', ADDON_ID),
            ('Authorization', "Basic %s" % base64string.decode('utf-8'))
        ]

    urllib_request.install_opener(opener)
    # Make requests to the ElementumD service bypass System proxy settings
    os.environ['no_proxy'] = "localhost,127.0.0.1,%s" % ADDON.getSetting("remote_host")

    # Pause currently playing Elementum file to avoid doubling requests
    try:
        if xbmc.Player().isPlaying() and ADDON_ID in xbmc.Player().getPlayingFile():
            xbmc.Player().pause()
    except:
        pass

    raw_url = sys.argv[2]
    parsed_url = urllib_parse.urlsplit(raw_url)
    parsed_query = urllib_parse.parse_qsl(parsed_url.query)
    encoded_query = urllib_parse.urlencode(parsed_query)
    encoded_url = urllib_parse.urlunsplit((parsed_url.scheme, parsed_url.netloc, parsed_url.path, encoded_query, parsed_url.fragment))
    url = sys.argv[0].replace("plugin://%s" % ADDON_ID, ELEMENTUMD_HOST + url_suffix) + encoded_url
    query_add = ""

    if len(sys.argv) > 3:
        query_add = sys.argv[3].replace(":", "=")

    if query_add and "resume=" not in url:
        query_add = query_add.replace("resume=", "doresume=")
        if "?" in url:
            url += "&" + query_add
        else:
            url += "?" + query_add

    log.debug("Requesting %s from %s" % (url, repr(sys.argv)))

    try:
# BEGIN ElementumService-patched: retry
#        data = _json(url)
        from elementum.addon import retryStartApp
        try:
            data = _json(url)
        except ConnectionResetError:
            retryStartApp()
            raise
        except urllib_error.URLError as e:
            if isinstance(e.reason, IOError) or \
                    isinstance(e.reason, OSError) or \
                    'Connection refused' in e.reason:
                retryStartApp()
            raise
# END ElementumService-patched: retry
    except PlayerException as e:
        redirect_url = e.__str__()
        log.debug("Launching player with %s" % (redirect_url))
        xbmcplugin.endOfDirectory(HANDLE, succeeded=True)
        xbmc.sleep(500)
        xbmc.executeJSONRPC('{"jsonrpc":"2.0","method":"Player.Open","params":{"item":{"file":"%s"}},"id":"1"}' % (redirect_url))
        return
    except RedirectException as e:
        redirect_url = e.__str__()
        log.debug("Redirecting Kodi with %s" % (redirect_url))
        xbmcplugin.endOfDirectory(HANDLE, succeeded=True)
        xbmc.sleep(500)
        if "keyboard=1" in sys.argv[0]:
            xbmc.executebuiltin('Container.Update(%s,replace)' % (redirect_url))
        else:
            xbmc.executebuiltin('Container.Update(%s)' % (redirect_url))
        return
    except urllib_error.URLError as e:
        # We can retry the request if connection is refused.
        # For example when plugin has not yet started but is requested by someone.
        if retry <= 2:
            time.sleep(preload_timeout)
            return run(retry=retry + 1)

        if isinstance(e.reason, IOError) or isinstance(e.reason, OSError) or 'Connection refused' in e.reason:
            notify(getLocalizedString(30116), time=7000)
        else:
            import traceback
            map(log.error, traceback.format_exc().split("\n"))
            notify(e.reason, time=7000)
        return
    except Exception as e:
        import traceback
        log.debug(traceback.print_exc())
        map(log.error, traceback.format_exc().split("\n"))
        try:
            msg = six.ensure_text(e.__str__(), errors='ignore')
        except:
            try:
                msg = six.ensure_binary(e.__str__(), errors='ignore')
            except:
                msg = repr(e)
        notify(getLocalizedLabel(msg), time=7000)
        return

    if not data:
        return

    if data["content_type"]:
        content_type = data["content_type"]
        if not content_type:
            content_type = ""
        elif "_" in content_type:
            content_type = ""

        xbmcplugin.addSortMethod(HANDLE, xbmcplugin.SORT_METHOD_UNSORTED)
        if content_type:
            try:
                # Movies
                if content_type != "tvshows":
                    xbmcplugin.addSortMethod(HANDLE, xbmcplugin.SORT_METHOD_LABEL_IGNORE_THE)
                    xbmcplugin.addSortMethod(HANDLE, xbmcplugin.SORT_METHOD_VIDEO_YEAR)
                # Shows
                else:
                    xbmcplugin.addSortMethod(HANDLE, xbmcplugin.SORT_METHOD_TITLE_IGNORE_THE)
                    xbmcplugin.addSortMethod(HANDLE, xbmcplugin.SORT_METHOD_VIDEO_YEAR)

                xbmcplugin.addSortMethod(HANDLE, xbmcplugin.SORT_METHOD_VIDEO_RATING)
                xbmcplugin.addSortMethod(HANDLE, xbmcplugin.SORT_METHOD_DATE)

                xbmcplugin.addSortMethod(HANDLE, xbmcplugin.SORT_METHOD_GENRE)
            except Exception as e:
                log.info("Could not set sorting: %s" % e)
                pass

        xbmcplugin.setContent(HANDLE, content_type)

    listitems = list(range(len(data["items"])))
    for i, item in enumerate(data["items"]):
        # Translate labels
        if "LOCALIZE" in item["label"]:
            if item["label"][0:8] == "LOCALIZE":
                item["label"] = getLocalizedLabel(item["label"])
            else:
                item["label"] = getLocalizedText(item["label"])
            if isinstance(item["label"], str):
                item["label"] = six.ensure_text(item["label"], 'utf-8')
        if "LOCALIZE" in item["label2"]:
            if item["label2"][0:8] == "LOCALIZE":
                item["label2"] = getLocalizedText(item["label2"])
            else:
                item["label2"] = getLocalizedText(item["label2"])
            if isinstance(item["label2"], str):
                item["label2"] = six.ensure_text(item["label2"], 'utf-8')

        if PLATFORM['kodi'] >= 19:
            listItem = xbmcgui.ListItem(
                label=item["label"],
                label2=item["label2"]
            )
            listItem.setArt({'icon': item["icon"]})
            listItem.setArt({'thumb': item["thumbnail"]})
        else:
            listItem = xbmcgui.ListItem(
                label=item["label"],
                label2=item["label2"],
                iconImage=item["icon"],
                thumbnailImage=item["thumbnail"]
            )
        if PLATFORM['kodi'] >= 20:
            video_info_tag = listItem.getVideoInfoTag()

        try:
            if item.get("castmembers"):
                if PLATFORM['kodi'] >= 20:
                    actors = []
                    for castmember in item['castmembers']:
                        actor = xbmc.Actor(name=castmember.get('name', ''),
                                           role=castmember.get('role', ''),
                                           thumbnail=castmember.get('thumbnail', ''),
                                           order=castmember.get('order', -1))
                        actors.append(actor)
                    video_info_tag.setCast(actors)
                elif PLATFORM['kodi'] >= 17:
                    listItem.setCast(item["castmembers"])
            if item.get("info"):
                item["info"] = normalizeLabels(item["info"])
                remove_dbtype_from_list(item["info"])
                listItem.setInfo("video", item["info"])
            if item.get("stream_info"):
                if PLATFORM['kodi'] >= 20:
                    for type_, values in item["stream_info"].items():
                        if type_ == "video":
                            video_stream_detail = xbmc.VideoStreamDetail(
                                width=values.get('width', 0),
                                height=values.get('height', 0),
                                aspect=values.get('aspect', 0),
                                duration=values.get('duration', 0),
                                codec=values.get('codec', ''),
                                language=values.get('language', ''),
                            )
                            video_info_tag.addVideoStream(video_stream_detail)
                        elif type_ == "audio":
                            audio_stream_detail = xbmc.AudioStreamDetail(
                                language=values.get('language', ''),
                                codec=values.get('codec', ''),
                                channels=values.get('channels', -1),
                            )
                            video_info_tag.addAudioStream(audio_stream_detail)
                        elif type_ == "subtitle":
                            subtitle_stream_detail = xbmc.SubtitleStreamDetail(
                                language=values.get('language', ''),
                            )
                            video_info_tag.addSubtitleStream(subtitle_stream_detail)
                else:
                    for type_, values in item["stream_info"].items():
                        listItem.addStreamInfo(type_, values)
            if item.get("uniqueids"):
                # values for setUniqueIDs() must be str
                if "Kodi" in item["uniqueids"]:
                    item["uniqueids"]["Kodi"] = str(item["uniqueids"]["Kodi"])
                if PLATFORM['kodi'] >= 20:
                    video_info_tag.setUniqueIDs(item["uniqueids"])
                else:
                    listItem.setUniqueIDs(item["uniqueids"])
            if item.get("art"):
                if "fanarts" in item.get("art") and item.get("art")["fanarts"]:
                    try:
                        start = 0
                        fanart_list = []
                        for fa in item.get("art")["fanarts"]:
                            start += 1
                            if PLATFORM['kodi'] < 18:
                                item.get("art")["fanart{}".format(start)] = fa
                            fanart_list.append({'image': fa})

                        if PLATFORM['kodi'] >= 18:
                            listItem.setAvailableFanart(fanart_list)
                    except Exception as e:
                        log.warning("Could not initialize ListItem.Art (%s): %s" % (repr(item.get("art")), repr(e)))
                        pass
                    del item.get("art")["fanarts"]

                if "available_artworks" in item.get("art"):
                    if item.get("art")["available_artworks"]:
                        if PLATFORM['kodi'] >= 18:
                            try:
                                for artwork_type, artworks in item.get("art")["available_artworks"].items():
                                    for artwork in artworks:
                                        if PLATFORM['kodi'] >= 20:
                                            video_info_tag.addAvailableArtwork(artwork, artwork_type)
                                        else:
                                            listItem.addAvailableArtwork(artwork, artwork_type)
                            except Exception as e:
                                log.warning("Could not initialize ListItem.Art (%s): %s" % (repr(item.get("art")), repr(e)))
                                pass
                    del item.get("art")["available_artworks"]
                listItem.setArt(item["art"])
            elif ADDON.getSetting('default_fanart') == 'true' and item["label"] != six.ensure_text(getLocalizedString(30218), 'utf-8'):
                fanart = os.path.join(ADDON_PATH, "fanart.jpg")
                listItem.setArt({'fanart': fanart})
            if item.get("context_menu"):
                # Translate context menus
                for m, menu in enumerate(item["context_menu"]):
                    if menu[0][0:8] == "LOCALIZE":
                        menu[0] = getLocalizedLabel(menu[0])
                    if PLATFORM['kodi'] >= 19 and menu[1][0:5] == "XBMC.":
                        menu[1] = menu[1][5:]
                listItem.addContextMenuItems(item["context_menu"])
            listItem.setProperty("isPlayable", item["is_playable"] and "true" or "false")
            if item.get("properties"):
                if PLATFORM['kodi'] >= 20:
                    if "resumetime" in item["properties"] and "totaltime" in item["properties"]:
                        video_info_tag.setResumePoint(
                            float(item["properties"]["resumetime"]),
                            float(item["properties"]["totaltime"]),
                        )
                        del item["properties"]["resumetime"]
                        del item["properties"]["totaltime"]
                for k, v in item["properties"].items():
                    listItem.setProperty(k, v)
        except Exception as e:
            log.warning("Could not initialize ListItem (%s): %s" % (repr(item.get("info")), repr(e)))
        listitems[i] = (item["path"], listItem, not item["is_playable"])

    xbmcplugin.addDirectoryItems(HANDLE, listitems, totalItems=len(listitems))

    # Set ViewMode
    if data["content_type"]:
        viewMode = ADDON.getSetting("viewmode_%s" % data["content_type"])
        if viewMode and viewMode != "0":
            try:
                xbmc.executebuiltin('Container.SetViewMode(%s)' % viewMode)
            except Exception as e:
                log.warning("Unable to SetViewMode(%s): %s" % (viewMode, repr(e)))

    xbmcplugin.endOfDirectory(HANDLE, succeeded=True, updateListing=False, cacheToDisc=True)
