# -*- coding: utf-8 -*-
"""The "Ceny AI" card: what the AI costs, by model and purpose, and the history of the calls, with deleting.

A modal card in the look of the Millionaire game (games/DialogAICosts.xml, generated by scripts/game_dialog/
gen_ai_costs.py); opened from the hero buttons of the Milionář section (`action=ai_costs`). The data comes from
wcs/ai/usage_view.py, the history from wcs/ai/usage_store.py. Pages: overview, models, purposes, history and the detail
of a history entry (its calls).
"""
import os

import xbmc
import xbmcaddon
import xbmcgui
import xbmcvfs

from wcs.ai import usage_store as store
from wcs.ai import usage_view as view
from wcs.ai.UsageTracker import format_cost, usd_to_czk

XML = 'games/DialogAICosts.xml'
ACTION_PREVIOUS_MENU = 10
ACTION_NAV_BACK = 92

PAGE_CONTROLS = {9701: 'overview', 9702: 'models', 9703: 'purposes', 9704: 'history'}
TAB_CONTROL = {page: control for control, page in PAGE_CONTROLS.items()}
CHIP_CONTROLS = {9711: 'today', 9712: 'week', 9713: 'month', 9714: 'all'}
DELETE, EXPORT, CLOSE = 9761, 9762, 9763
LIST = 9720
BAR_FIRST, BAR_COUNT = 9801, 30
CHART_X, CHART_W, CHART_BASE, CHART_TOP = 24, 1196, 158, 84           # inside the chart panel, as drawn by gen_ai_costs.py
BAR_WIDTH, BAR_MIN = 28, 3
GOLD, TODAY, EMPTY = 'FFE8B860', 'FFFBFBFD', '33FFFFFF'

# column titles of each page: property -> string name
COLUMNS = {
    'models': {'Col1': 'col_model', 'Col3': 'col_calls', 'Col4': 'col_share', 'Col5': 'col_cost'},
    'purposes': {'Col1': 'col_purpose_tokens', 'Col3': 'col_calls', 'Col4': 'col_share', 'Col5': 'col_cost'},
    'history': {'Col1': 'col_entry', 'Col3': 'col_calls', 'Col4': 'col_when', 'Col5': 'col_cost'},
    'detail': {'Col1': 'col_time', 'Col3': 'col_response', 'Col4': 'col_purpose', 'Col5': 'col_cost'},
}


def show_ai_costs():
    """Opens the card and returns when it is closed."""
    addon = xbmcaddon.Addon()
    window = AICostsDialog(XML, addon.getAddonInfo('path'), 'Default', '1080i')
    window.doModal()
    del window


class AICostsDialog(xbmcgui.WindowXMLDialog):
    def __init__(self, xml_filename, script_path, skin='Default', theme='1080i'):
        super().__init__(xml_filename, script_path, skin, theme)
        self.addon = xbmcaddon.Addon()
        self.texts = view.Texts(view.load_strings(self.addon), self.addon)
        self.page = 'overview'
        self.period = view.DEFAULT_PERIOD
        self.detail_key = ''
        self.detail_title = ''
        self.detail_origin = 'history'
        self.rows = []

    # ---- life
    def onInit(self):
        s = self.texts.s
        title = s['title'].upper()
        self.setProperty('AICosts.TitleSpaced', '  '.join(' '.join(word) for word in title.split()))
        self.setProperty('AICosts.Rate', s['rate'].format('{:.2f}'.format(usd_to_czk()).replace('.', ',')))
        self.setProperty('AICosts.Period', self.period)
        self._show('overview')

    def onAction(self, action):
        if action.getId() in (ACTION_PREVIOUS_MENU, ACTION_NAV_BACK):
            if self.page == 'detail':
                self._show(self.detail_origin)
                self.setFocusId(LIST)
            else:
                self.close()
            return
        super().onAction(action)

    def onFocus(self, control_id):
        """The page buttons work like tabs: focusing one opens its page."""
        page = PAGE_CONTROLS.get(control_id)
        if page and page != self._tab():
            self._show(page)

    def onClick(self, control_id):
        if control_id in PAGE_CONTROLS:
            self.setFocusId(LIST)
        elif control_id in CHIP_CONTROLS:
            self.period = CHIP_CONTROLS[control_id]
            self.setProperty('AICosts.Period', self.period)
            self._show(self._tab())
        elif control_id == LIST:
            self._open_row()
        elif control_id == DELETE:
            self._delete()
        elif control_id == EXPORT:
            self._export()
        elif control_id == CLOSE:
            self.close()

    # ---- pages
    def _tab(self):
        """The page button that is lit: the detail of a history entry belongs to History."""
        return 'history' if self.page == 'detail' else self.page

    def _show(self, page, keep_position=False):
        self.page = page
        self.setProperty('AICosts.Page', page)
        position = self._position() if keep_position else 0
        s = self.texts.s
        if page == 'overview':
            data = view.overview(self.texts, self.period)
            for name in ('Cost', 'Calls', 'Tokens', 'Savings'):
                self.setProperty('AICosts.' + name, data[name.lower()])
                self.setProperty('AICosts.{}Note'.format(name), data[name.lower() + '_note'])
                self.setProperty('AICosts.{}Note2'.format(name), data[name.lower() + '_note2'])
            self.setProperty('AICosts.Facts', '{} {}  ·  {} {}  ·  {} {}'.format(
                s['today_label'], data['today'], s['month_label'], data['month'], s['overall_label'], data['overall']))
            self._draw_chart(data)
            rows = data['rows']
        else:
            rows = {'models': lambda: view.models(self.texts, self.period),
                    'purposes': lambda: view.purposes(self.texts, self.period),
                    'history': lambda: view.history(self.texts, self.period),
                    'detail': lambda: view.detail(self.texts, self.detail_key)}[page]()
            self.setProperty('AICosts.Heading', self.detail_title if page == 'detail' else s[page])
            for column, name in COLUMNS[page].items():
                self.setProperty('AICosts.' + column, s[name])
        self._fill(rows or [{'kind': 'empty', 'key': '', 'c1': s['empty']}], position)
        tab = self.getControl(TAB_CONTROL[self._tab()])                      # Left from the content returns to the open page
        self.getControl(LIST).controlLeft(tab)
        self.getControl(min(CHIP_CONTROLS)).controlLeft(tab)

    def _fill(self, rows, position=0):
        self.rows = rows
        items = []
        for row in rows:
            item = xbmcgui.ListItem(label=row['c1'])
            for column in ('c1', 'c2', 'c3', 'c4', 'c5'):
                item.setProperty(column, row.get(column, ''))
            item.setProperty('kind', row['kind'])
            item.setProperty('error', 'true' if row.get('error') else 'false')
            has_bar = row['kind'] in ('model', 'purpose')
            item.setProperty('bar', 'true' if has_bar else 'false')
            item.setProperty('share', str(row.get('share', 0)))
            items.append(item)
        control = self.getControl(LIST)
        control.reset()
        control.addItems(items)
        control.selectItem(min(position, len(items) - 1))

    def _draw_chart(self, data):
        """The daily bars: 30 images the skin declares, placed here because their heights are data."""
        days = data['chart']
        peak = max([day['cost'] for day in days] or [0])
        step = CHART_W / float(BAR_COUNT)
        for index in range(BAR_COUNT):
            bar = self.getControl(BAR_FIRST + index)
            cost = days[index]['cost'] if index < len(days) else 0
            height = max(BAR_MIN, int(round((CHART_BASE - CHART_TOP) * cost / peak))) if peak > 0 and cost > 0 else BAR_MIN
            bar.setPosition(int(CHART_X + index * step + (step - BAR_WIDTH) / 2), CHART_BASE - height)
            bar.setWidth(BAR_WIDTH)
            bar.setHeight(height)
            bar.setColorDiffuse(EMPTY if cost <= 0 else TODAY if index == len(days) - 1 else GOLD)
        first, last = days[0]['day'], days[-1]['day']
        self.setProperty('AICosts.ChartFrom', '{}. {}.'.format(int(first[8:10]), int(first[5:7])))
        self.setProperty('AICosts.ChartTo', self.texts.s['today'])
        self.setProperty('AICosts.ChartMax', data['chart_max'] if peak > 0 else '')

    # ---- the list
    def _position(self):
        return max(0, self.getControl(LIST).getSelectedPosition())

    def _row(self):
        position = self._position()
        return self.rows[position] if 0 <= position < len(self.rows) else {}

    def _open_row(self):
        row = self._row()
        if row.get('kind') == 'entry':
            self.detail_origin = self.page if self.page in ('overview', 'history') else 'history'
            self.detail_key, self.detail_title = row['key'], row['c1']
            self._show('detail')

    # ---- deleting
    def _delete(self):
        s = self.texts.s
        row = self._row()
        choices = []                                                     # (label, scope kind, value, label of the confirmation)
        if row.get('kind') == 'call':
            choices.append((s['delete_call'], 'call', row['call_id']))
        if row.get('kind') == 'entry' or self.page == 'detail':
            key = row['key'] if row.get('kind') == 'entry' else self.detail_key
            choices.append((s['delete_entry'].format(row['c1'] if row.get('kind') == 'entry' else self.detail_title), 'entry', key))
        if row.get('day'):
            choices.append((s['delete_day'].format(self._date(row['day'])), 'day', row['day']))
            choices.append((s['delete_month'].format('{}. {}'.format(int(row['day'][5:7]), row['day'][:4])), 'month', row['day'][:7]))
        if row.get('kind') == 'model':
            choices.append((s['delete_provider'].format(row['provider']), 'provider', row['provider']))
        choices.append((s['delete_all'], 'all', None))
        chosen = xbmcgui.Dialog().select(s['delete_title'], [label for label, _, _ in choices])
        if chosen < 0:
            return
        label, kind, value = choices[chosen]
        preview = store.preview_delete(kind, value)
        if not preview['records']:
            xbmcgui.Dialog().notification(s['title'], s['nothing'], xbmcgui.NOTIFICATION_INFO, 2500)
            return
        text = s['delete_confirm'].format(what=label, count=preview['requests'], cost=format_cost(preview['cost'], self.addon))
        if not xbmcgui.Dialog().yesno(s['delete_title'], text, yeslabel=s['delete_short']):
            return
        store.delete(kind, value)
        xbmcgui.Dialog().notification(s['title'], s['deleted'].format(label), xbmcgui.NOTIFICATION_INFO, 3000)
        self._refresh_after_delete(kind)

    @staticmethod
    def _date(day):
        return '{}. {}. {}'.format(int(day[8:10]), int(day[5:7]), day[:4])

    def _refresh_after_delete(self, kind):
        if self.page == 'detail' and (kind != 'call' or not store.calls_of(self.detail_key)):
            self._show(self.detail_origin)
        else:
            self._show(self.page, keep_position=True)

    # ---- export
    def _export(self):
        path = os.path.join(xbmcvfs.translatePath('special://profile/addon_data/plugin.video.milionar/'), 'ai_usage_export.csv')
        store.export_csv(path)
        xbmcgui.Dialog().notification(self.texts.s['title'], self.texts.s['exported'].format(path), xbmcgui.NOTIFICATION_INFO, 6000)
