# -*- coding: utf-8 -*-
import sys, json, os, time, datetime, urllib.request, urllib.parse
import xbmc, xbmcaddon, xbmcgui, xbmcplugin, xbmcvfs

ADDON = xbmcaddon.Addon(id='plugin.video.kodiflix.live')
HANDLE = int(sys.argv[1]) if len(sys.argv) > 1 and str(sys.argv[1]).isdigit() else -1
BASE = sys.argv[0]
DATA_DIR = xbmcvfs.translatePath(ADDON.getAddonInfo('profile'))
PREF_FILE = os.path.join(DATA_DIR, 'preferred_sources.json')

def endpoint(path):
    server = ADDON.getSetting('server_url').rstrip('/')
    if not server.startswith(('http://', 'https://')):
        raise ValueError('Configure KodiFlix Live server URL')
    key = ADDON.getSetting('api_key')
    if not key:
        raise ValueError('Configure KodiFlix Live API key')
    request = urllib.request.Request(server + path, headers={'X-Api-Key': key, 'Accept': 'application/json'})
    with urllib.request.urlopen(request, timeout=12) as response:
        return json.load(response)

def plugin_url(action, **kwargs):
    return BASE + '?' + urllib.parse.urlencode(dict(action=action, **kwargs))

def preferences():
    try:
        with open(PREF_FILE, 'r', encoding='utf-8') as f:
            data = json.load(f)
            return data if isinstance(data, dict) else {}
    except (OSError, ValueError):
        return {}

def save_preference(key, source_id):
    prefs = preferences()
    prefs[key] = int(source_id)
    os.makedirs(DATA_DIR, exist_ok=True)
    temp = PREF_FILE + '.tmp'
    with open(temp, 'w', encoding='utf-8') as f:
        json.dump(prefs, f)
    os.replace(temp, PREF_FILE)

def add_folder(label, action, **kwargs):
    item = xbmcgui.ListItem(label=label)
    item.setArt({'icon': 'DefaultFolder.png'})
    xbmcplugin.addDirectoryItem(HANDLE, plugin_url(action, **kwargs), item, True)

def channel_key(channel):
    return channel.get('category', '') + ':' + channel.get('label', '')

GROUPS = [('national','National News'),('nc-local','North Carolina Local News'),
          ('sports','Sports'),('entertainment','Entertainment'),
          ('broadcast','Broadcast'),('premium','Premium Movies')]
CACHE_TTL = 1800

def cache_path(category):
    return os.path.join(DATA_DIR, 'live_' + category + '.json')

def read_cache(category):
    try:
        with open(cache_path(category), 'r', encoding='utf-8') as f:
            obj = json.load(f)
        if isinstance(obj, dict) and isinstance(obj.get('channels'), list):
            return obj
    except (OSError, ValueError, TypeError):
        pass
    return None

def update_cache(category):
    # Two small, category-scoped API calls; never fetch all channels.
    grouped = endpoint('/api/live/election/grouped/' + category).get('items', [])
    start = int(time.time()) - 300
    guide = endpoint('/api/live/epg/guide?' + urllib.parse.urlencode(
        {'category': category, 'start': start, 'hours': 12})).get('items', [])
    schedules = {(r.get('category'), r.get('label')): r.get('programmes') or [] for r in guide}
    for c in grouped:
        c['programmes'] = schedules.get((c.get('category'), c.get('label')), [])
    data = {'updated': int(time.time()), 'channels': grouped}
    os.makedirs(DATA_DIR, exist_ok=True)
    target = cache_path(category)
    temp = target + '.tmp'
    with open(temp, 'w', encoding='utf-8') as f:
        json.dump(data, f, separators=(',', ':'))
    os.replace(temp, target)
    return data

def schedule(channel, now):
    programmes = sorted(channel.get('programmes') or [], key=lambda p: (p.get('start_ts', 0), p.get('end_ts', 0)))
    playing = next((p for p in programmes if int(p.get('start_ts', 0)) <= now < int(p.get('end_ts', 0))), None)
    upcoming = next((p for p in programmes if int(p.get('start_ts', 0)) > now), None)
    return playing, upcoming

def clock(ts):
    return datetime.datetime.fromtimestamp(int(ts)).strftime('%I:%M %p').lstrip('0')

def listing(category):
    if category not in dict(GROUPS):
        raise ValueError('Unknown channel group')
    data = read_cache(category)
    # The service keeps the cache fresh. On first installation, fetch once.
    if data is None:
        data = update_cache(category)
    prefs = preferences()
    now = int(time.time())
    add_folder('[COLOR cyan]Open Stacked Visual Guide[/COLOR]', 'stacked', category=category)
    for channel in data.get('channels', []):
        sources = channel.get('sources') or []
        if not sources:
            continue
        label = channel.get('label') or 'Unknown channel'
        key = channel_key(channel)
        ids = [int(s['id']) for s in sources]
        preferred = prefs.get(key)
        if preferred not in ids:
            preferred = ids[0]
        playing, upcoming = schedule(channel, now)
        current_title = playing.get('title', 'No schedule available') if playing else 'No schedule available'
        next_title = upcoming.get('title', 'Schedule unavailable') if upcoming else 'Schedule unavailable'
        next_time = clock(upcoming['start_ts']) if upcoming else ''
        title = '{}  |  NOW: {}  |  NEXT: {} {}'.format(label, current_title, next_time, next_title)
        item = xbmcgui.ListItem(label=title)
        item.setProperty('IsPlayable', 'true')
        item.setProperty('kodiflix.channel', label)
        item.setProperty('kodiflix.now', current_title)
        item.setProperty('kodiflix.next', next_title)
        item.setProperty('kodiflix.next_time', next_time)
        item.setProperty('kodiflix.group', category)
        if playing:
            duration = max(1, int(playing['end_ts']) - int(playing['start_ts']))
            progress = max(0, min(100, int(100 * (now-int(playing['start_ts'])) / duration)))
            item.setProperty('kodiflix.progress', str(progress))
            item.setProperty('kodiflix.remaining', str(max(0, (int(playing['end_ts'])-now+59)//60)))
        description = (playing.get('description') or '') if playing else ''
        plot = 'PLAYING NOW: {}\nUP NEXT: {} {}\n\n{}\n\n{} available sources'.format(
            current_title, next_time, next_title, description, len(sources))
        item.setInfo('video', {'title': current_title if playing else label, 'mediatype': 'video', 'plot': plot})
        logo = channel.get('logo') or ''
        artwork = (playing.get('icon') or '') if playing else ''
        art = {'icon': logo or 'DefaultTVShows.png', 'thumb': artwork or logo or 'DefaultTVShows.png'}
        if artwork:
            art['fanart'] = artwork
        item.setArt(art)
        item.addContextMenuItems([
            ('Set preferred stream', 'RunPlugin({})'.format(plugin_url('choose_preferred', category=category, label=label))),
            ('Browse / test all streams', 'Container.Update({})'.format(plugin_url('sources', category=category, label=label))),
        ])
        xbmcplugin.addDirectoryItem(HANDLE, plugin_url('play', id=preferred), item, False)
    xbmcplugin.setContent(HANDLE, 'videos')
    xbmcplugin.addSortMethod(HANDLE, xbmcplugin.SORT_METHOD_UNSORTED)

def get_channel(category, label):
    suffix = '' if category == 'all' else '/' + category
    data = endpoint('/api/live/election/grouped' + suffix)
    channel = next((x for x in data.get('items', []) if x.get('label') == label), None)
    if channel is None:
        raise ValueError('Channel no longer in catalog')
    return channel

def choose_preferred(category, label):
    channel = get_channel(category, label)
    options = channel.get('sources') or []
    if not options:
        raise ValueError('No available sources')
    key = channel_key(channel)
    preferred = preferences().get(key)
    labels = []
    for i, source in enumerate(options, 1):
        mark = ' [PREFERRED]' if preferred == int(source['id']) else ''
        labels.append('Source {}: {}{}'.format(i, source.get('name') or label, mark))
    chosen = xbmcgui.Dialog().select('Preferred stream: ' + label, labels, preselect=next((i for i,s in enumerate(options) if int(s['id']) == preferred), -1))
    if chosen < 0:
        return
    save_preference(key, options[chosen]['id'])
    xbmcgui.Dialog().notification('KodiFlix Live', 'Preferred stream saved for ' + label, xbmcgui.NOTIFICATION_INFO, 3000)
    xbmc.executebuiltin('Container.Refresh')

def sources(category, label):
    suffix = '' if category == 'all' else '/' + category
    data = endpoint('/api/live/election/grouped' + suffix)
    channel = next((x for x in data.get('items', []) if x.get('label') == label), None)
    if channel is None:
        raise ValueError('Channel no longer in catalog')
    key = channel_key(channel)
    pref = preferences().get(key)
    for i, source in enumerate(channel.get('sources') or [], 1):
        source_id = int(source['id'])
        selected = ' [PREFERRED]' if pref == source_id else ''
        title = 'Source {}: {}{}'.format(i, source.get('name') or label, selected)
        item = xbmcgui.ListItem(label=title)
        item.setProperty('IsPlayable', 'true')
        item.setInfo('video', {'title': label, 'mediatype': 'video'})
        item.addContextMenuItems([
            ('Set as preferred', 'RunPlugin({})'.format(plugin_url('prefer', key=key, id=source_id)))
        ])
        xbmcplugin.addDirectoryItem(HANDLE, plugin_url('play', id=source_id), item, False)
    xbmcplugin.setContent(HANDLE, 'videos')

def relay_url(url):
    import urllib.request
    payload = json.dumps({'url': url}).encode('utf-8')
    request = urllib.request.Request('http://127.0.0.1:18877/register', data=payload,
                                     headers={'Content-Type': 'application/json'}, method='POST')
    with urllib.request.urlopen(request, timeout=2) as response:
        result = json.load(response)
    path = result['path']
    if not path.startswith('/live/') or not path.endswith('.ts'):
        raise ValueError('Invalid relay response')
    return 'http://127.0.0.1:18877' + path

def play(source_id):
    data = endpoint('/api/live/channel/' + str(int(source_id)))
    url = data.get('url', '')
    if not url.startswith(('http://', 'https://')):
        raise ValueError('Invalid stream URL')
    if ADDON.getSetting('use_relay') != 'false':
        try:
            url = relay_url(url)
        except Exception as exc:
            xbmc.log('KodiFlix Live relay unavailable, using direct stream: {}'.format(type(exc).__name__), xbmc.LOGWARNING)
    item = xbmcgui.ListItem(path=url)
    item.setProperty('IsPlayable', 'true')
    item.setInfo('video', {'title': data.get('label', 'Live TV'), 'mediatype': 'video'})
    xbmcplugin.setResolvedUrl(HANDLE, True, item)

def main():
    params = urllib.parse.parse_qs(sys.argv[2].lstrip('?'))
    action = params.get('action', ['root'])[0]
    try:
        if action == 'play':
            play(params.get('id', ['0'])[0])
            return
        if action == 'choose_preferred':
            choose_preferred(params.get('category', ['all'])[0], params.get('label', [''])[0])
            return
        if action == 'prefer':
            save_preference(params.get('key', [''])[0], params.get('id', ['0'])[0])
            xbmcgui.Dialog().notification('KodiFlix Live', 'Preferred source saved', xbmcgui.NOTIFICATION_INFO, 3000)
            xbmc.executebuiltin('Container.Refresh')
            return
        if action == 'stacked':
            category = params.get('category', ['national'])[0]
            if category not in dict(GROUPS):
                raise ValueError('Unknown group')
            launcher = os.path.join(ADDON.getAddonInfo('path'), 'stacked_launcher.py')
            xbmcplugin.endOfDirectory(HANDLE, succeeded=True, updateListing=False, cacheToDisc=False)
            xbmc.executebuiltin('RunScript("{}",{})'.format(launcher, category))
            return
        if action == 'guide':
            # Launch independently of the directory plugin invocation.
            # Blocking here leaves Kodi's busy spinner active throughout playback.
            launcher = os.path.join(ADDON.getAddonInfo('path'), 'launcher.py')
            xbmc.executebuiltin('RunScript(\"{}\")'.format(launcher))
            return
        if action == 'root':
            for category, title in GROUPS:
                add_folder(title, 'list', category=category)
        elif action == 'list':
            listing(params.get('category', ['all'])[0])
        elif action == 'sources':
            sources(params.get('category', ['all'])[0], params.get('label', [''])[0])
    except Exception as exc:
        xbmc.log('KodiFlix Live: {}: {}'.format(type(exc).__name__, str(exc).split('?')[0][:120]), xbmc.LOGERROR)
        xbmcgui.Dialog().notification('KodiFlix Live', str(exc)[:140], xbmcgui.NOTIFICATION_ERROR, 6000)
        if action == 'play':
            xbmcplugin.setResolvedUrl(HANDLE, False, xbmcgui.ListItem())
    finally:
        if action in ('root', 'list', 'sources', 'guide'):
            xbmcplugin.endOfDirectory(HANDLE, cacheToDisc=False)

if __name__ == '__main__':
    main()
