"""Propriecle grab bag"""
import sys
from random import SystemRandom
import curses
import logging
import traceback
import hvac
from propriecle.ui import string_at, middle_yx, yesno, \
    popup, init_screen, ask_for
from propriecle.helpers import problems, finish
from propriecle.vault import seal, unseal, \
    rekey_start, rotate_master, regenerate_start, init, \
    regenerate_enter, regenerate_cancel, rekey_enter, rekey_cancel, \
    step_down
from propriecle.keys import list_keys, grok_keys
from propriecle.servers import get_server
import propriecle.conf as conf
LOG = logging.getLogger(__name__)

def update_servers():
    """Kicks off the update process for our Vault servers"""
    servers = []
    for server in conf.get('vaults'):
        servers.append(get_server(server['name']))

    return servers


def do_seal(screen, server):
    """Invoke the GUI action of sealing Vault"""
    name = server['name']

    seal_msg = "SEAL %s" % name
    if not yesno(screen, seal_msg):
        return
    screen.refresh()

    try:
        if not seal(server):
            popup(screen, "Unable to seal!")
    except hvac.exceptions.Forbidden:
        popup(screen, "Invalid root token!")
        return


def do_rekey(server):
    """Invoke the GUI action of rekeying unseal keys"""
    if not conf.get('keys') or not conf.get('required'):
        problems('keys and required missing from config')

    rekey_start(server, grok_keys())


def get_keys(screen, server):
    """Generates a list of keys for the user to select from.
    Options also include manually entering a key or using
    any valid key"""
    client = server['client']
    keys = list_keys(server)
    middle_y, middle_x = middle_yx(screen)
    my_x = 30
    my_y = len(keys) + 6
    win_x = middle_x - (my_x/2)
    win_y = middle_y - 10
    win = curses.newwin(my_y, my_x, win_y, win_x)
    status = client.seal_status

    done = False
    selection = 0
    return_keys = []
    while not done:
        win.border()
        win.bkgd(ord(' '), curses.color_pair(1))
        win.addstr(1, 1, "Select Key (%s of %s complete)" %
                   (status['progress'], status['t']),
                   curses.color_pair(2))
        win.addstr(my_y - 2, 1, "[m]anual [a]ny", curses.color_pair(1))
        for key_obj in keys:
            name = key_obj['name']
            i = key_obj['index']
            color = curses.color_pair(2)
            if not key_obj['key']:
                color = curses.color_pair(4)

            if selection == i:
                color = curses.color_pair(6)

            if name.startswith('keybase:'):
                name = name[8:]

            win.addstr(i + 2, 1, "[%s] %s" % (i + 1, name), color)

        win.refresh()
        screen.timeout(500)
        ch = screen.getch()
        if ch == 27:
            del win
            screen.refresh()
            return
        elif ch == curses.KEY_DOWN:
            down_done = False
            while not down_done:
                if selection < len(keys) - 1 and \
                   keys[selection + 1]['key']:
                    selection = selection + 1
                    down_done = True
                elif selection == len(keys) - 1:
                    down_done = True
                else:
                    selection = selection + 1
        elif ch == curses.KEY_UP:
            up_done = False
            while not up_done:
                if selection > 0 and \
                   keys[selection - 1]['key']:
                    selection = selection - 1
                    up_done = True
                elif selection == 0:
                    up_done = True
                else:
                    selection = selection - 1
        elif ch == ord('m') or ch == ord('M'):
            return ask_for(screen, "Key")
        elif ch == ord('a') or ch == ord('A'):
            return_keys = [k for k in keys if k['key']][0:conf.get('required')]
            done = True
        elif ch == 10:
            if keys[selection]['key']:
                done = True

    win.erase()
    win.refresh()
    del win

    if len(return_keys) == 0:
        return_keys = [k for k in keys if k['index'] == selection]

    return return_keys


def do_unseal(screen, server):
    """Invoke the GUI action of unsealing a Vault server"""
    client = server['client']
    status = client.seal_status
    if not status['sealed']:
        popup(screen, "Not actually sealed")
        return

    for key_obj in do_get_keys(screen, server):
        try:
            unseal(client, key_obj['key'])
        except hvac.exceptions.InvalidRequest:
            popup(screen, "Unable to unseal with %s:%s" %
                  (key_obj['index'], key_obj['name']))
            popup(screen, "Invalid Key!")


def refresh_screen(screen, selection, main_win=None):
    """Refreshes the GUI elements of the main screen"""
    y, x = screen.getmaxyx()
    main_x = x - (x / 4)
    main_y = len(conf.get('vaults')) + 5
    if y < main_y or x < main_x:
        problems("screen too small")

    screen.border()
    screen.bkgd(ord(' '), curses.color_pair(1))

    middle_y, middle_x = middle_yx(screen)
    if not main_win:
        main_win = curses.newwin(main_y, main_x,
                                 int(round(middle_y - (main_y/2))),
                                 int(round(middle_x - (main_x/2))))
    else:
        main_win.erase()

    main_win.bkgd(ord(' '), curses.color_pair(1))
    main_win.border()
    string_at(main_win, 1, 0, conf.get("title", "Vault Friends"),
              curses.color_pair(1), 'center')
    refresh_status(main_win, selection)
    screen.nooutrefresh()
    main_win.nooutrefresh()
    curses.doupdate()
    return main_win


def refresh_initialized(main_win, server, index, selected):
    """Refreshes the lable for a initialized Vault instance"""
    y, x = main_win.getmaxyx()
    name = server['name']
    if server['init'] and not server['sealed'] and server['ha']:
        c_state = 'S'
        if server['leader']:
            c_state = 'L'

        name = "%s [%s %s]" % (server['name'], c_state, server['cluster_name'])

    name_msg = "[%s] %s" % (index, name)
    base_x = len(name_msg) + 1
    line = index + 3
    col = curses.color_pair(1)
    if selected:
        col = curses.color_pair(6)

    string_at(main_win, line, 0, name_msg, col)
    if server['sealed']:
        s_msg = "Sealed"
        string_at(main_win, line, base_x, s_msg, curses.color_pair(3))
    else:
        s_msg = 'OK'
        s_col = 2
        if server['rekey']:
            s_msg = 'REKEY'
            s_col = 7

        string_at(main_win, line, base_x, s_msg, curses.color_pair(s_col))


def randcolor():
    """A random pretty color plz"""
    s_color = 0
    s_rand = SystemRandom().randint(0, 2)
    if s_rand == 0:
        s_color = 1
    elif s_rand == 1:
        s_color = 3
    elif s_rand == 2:
        s_color = 7

    if SystemRandom().randint(0, 1) == 1:
        s_color = s_color | curses.A_BOLD

    return s_color


def refresh_uninitialized(main_win, server, selected, index):
    """Refreshes the label for a uninitialized Vault instance"""
    y, x = main_win.getmaxyx()
    name = server['name']
    name_msg = "[%s] %s" % (index, name)
    base_x = len(name_msg) + 1
    new_msg = 'NEW'
    line = index + 3
    col = curses.color_pair(1)
    if selected:
        col = curses.color_pair(6)

    string_at(main_win, line, 0, name_msg, col)
    string_at(main_win, line,
              base_x, new_msg,
              curses.color_pair(7) | curses.A_BOLD)
    s_anim = ['*', 'o', '.', '^', '_', '-', '|']

    for f in range(0, 5):
        string_at(main_win,
                  line,
                  base_x + len(new_msg) + 2 + f,
                  s_anim[SystemRandom().randint(0, len(s_anim) - 1)],
                  curses.color_pair(randcolor()))


def refresh_offline(main_win, server, selected, index):
    """Refreshes the label for a offline Vault instance"""
    y, x = main_win.getmaxyx()
    name = server['name']
    name_msg = "[%s] %s" % (index, name)
    base_x = len(name_msg) + 1
    new_msg = 'AWOL'
    line = index + 3
    col = curses.color_pair(1)
    if selected:
        col = curses.color_pair(6)

    string_at(main_win, line, 0, name_msg, col)
    string_at(main_win, line, base_x,
              new_msg, curses.color_pair(3) | curses.A_BOLD)


def refresh_status(main_win, selection):
    """Refreshes the label of a Vault instance"""
    y, x = main_win.getmaxyx()
    index = 0
    for server in update_servers():
        if 'init' in server:
            if server['init']:
                refresh_initialized(main_win, server,
                                    index, selection == index)
            else:
                refresh_uninitialized(main_win, server,
                                      selection == index, index)
        else:
            refresh_offline(main_win, server,
                            selection == index, index)

        index = index + 1


def focus_loop(screen, index):
    """Main interaction loop when an actual Vault instance is selected
    and we are looking at the detailed view."""
    done = False
    screen.erase()
    while not done:
        server = get_server(conf.get('vaults')[index]['name'])
        refresh_focused(screen, server)
        if focus_input(screen, server):
            screen.erase()
            return


def focus_input(screen, server):
    """User input for when we are in the detailed view of a Vault instance"""
    screen.timeout(500)
    ch = screen.getch()
    if ch == 27:
        return True

    if 'init' in server:
        if 255 > ch > 0:
            ch_s = chr(ch).lower()
            is_init = server['init']
            if is_init:
                is_sealed = server['sealed']
                if not is_sealed:
                    is_rekey = server['rekey']
                    is_regenerating = server['regenerating']
                    is_ha = server['ha']
                    if is_ha:
                        is_leader = server['leader']

            if ch_s == 's' and is_init and not is_sealed:
                if not is_ha or (is_ha and is_leader):
                    do_seal(screen, server)
            elif ch_s == 'u' and is_init and is_sealed:
                do_unseal(screen, server)
            elif ch_s == 'r' and is_init and not is_sealed and not is_rekey:
                do_rekey(server)
            elif ch_s == 'i' and not is_init:
                do_init(screen, server)
            elif ch_s == 'e' and is_init and is_rekey:
                do_rekey_enter(screen, server)
            elif ch_s == 'c' and is_init and is_rekey:
                if not rekey_cancel(server):
                    popup(screen, "Unable to cancel rekey")
            elif ch_s == 'e' and is_init and is_regenerating:
                do_regenerate_enter(screen, server)
            elif ch_s == 'c' and is_init and is_regenerating:
                if not regenerate_cancel(server):
                    popup(screen, "Unable to cancel regeneration")
            elif ch_s == 'o' and is_init and not is_rekey and \
                 not is_regenerating and not is_sealed:
                if not rotate_master(server):
                    popup(screen, "Unable to rotate master")
            elif ch_s == 'g' and is_init and \
                 not is_rekey and \
                 not is_regenerating and \
                 not is_sealed:
                regenerate_start(server)
            elif ch_s == 'p' and is_init and \
                 is_ha and is_leader:
                step_down(server)


def do_get_keys(screen, server):
    """Selecting keys from a screen I guess"""
    keys = get_keys(screen, server)
    screen.nooutrefresh()
    if not keys:
        popup(screen, "Must specify at least one key")
        return []

    return keys


def do_regenerate_enter(screen, server):
    """GUI context for submitting unseal keys when
    regenerating root token"""
    for key_obj in do_get_keys(screen, server):
        if not regenerate_enter(server, key_obj['key']):
            popup(screen, "Unable to regenerate with %s:%s" %
                  (key_obj['index'], key_obj['name']))


def do_rekey_enter(screen, server):
    """GUI context for submitting unseal keys when rekeying"""
    for key_obj in do_get_keys(screen, server):
        if not rekey_enter(server, key_obj['key']):
            popup(screen, "Unable to rekey with %s:%s" %
                  (key_obj['index'], key_obj['name']))


def refresh_focused(screen, server):
    """Redraw the focus/detail view of a Vault instance"""
    win_x = 30 + len(server['url'])
    win_y = 10
    middle_y, middle_x = middle_yx(screen)
    win = curses.newwin(win_y, win_x,
                        middle_y - (win_y / 2),
                        middle_x - (win_x / 2))
    win.border()
    win.bkgd(ord(' '), curses.color_pair(1))
    string_at(win, 1, 0, server['name'], curses.color_pair(1), 'center')
    string_at(win, 2, 0, server['url'], curses.color_pair(1))
    if 'init' in server:
        if server['init']:
            string_at(win, 3, 0, server['version'], curses.color_pair(1))
            if server['sealed']:
                msg_s = 'SEALED'
                string_at(win, 5, 0, msg_s,
                          curses.color_pair(3) | curses.A_BOLD)
                unseal_remaining = server['unseal_required'] \
                                   - server['unseal_progress']
                msg_details = "%s keys required" % (unseal_remaining)
                string_at(win, 5, len(msg_s) + 1,
                          msg_details, curses.color_pair(1))
            else:
                msg_s = ''
                if server['rekey']:
                    msg_s = 'REKEY'
                    string_at(win, 5, 0,
                              msg_s, curses.color_pair(7))
                    rekey_remaining = server['rekey_required'] \
                                      - server['rekey_progress']
                    string_at(win, 6, 0,
                              "%s keys required" % (rekey_remaining),
                              curses.color_pair(1))
                elif (not server['ha'] or (server['ha'] and server['leader'])) \
                     and server['regenerating']:
                    msg_s = 'REGEN'
                    string_at(win, 5, 0,
                              msg_s, curses.color_pair(7))
                    string_at(win, 6, 0,
                              "%s keys required" %
                              (server['regen_required'] - server['regen_progress']),
                              curses.color_pair(1))
                else:
                    msg_s = 'OK'
                    string_at(win, 5, 0, msg_s, curses.color_pair(2))
                    if 'key_term' in server:
                        string_at(win, 6, 0,
                                  "Key Term %s" % server['key_term'],
                                  curses.color_pair(1))

                ha_msg = "HA"
                if server['ha']:
                    string_at(win, 5, len(msg_s) + 1,
                              ha_msg, curses.color_pair(1))
                    if server['leader']:
                        string_at(win, 5,
                                  len(msg_s) + len(ha_msg) +
                                  len(server['cluster_name']) + 3,
                                  "Leader", curses.color_pair(1))
                    else:
                        string_at(win, 5,
                                  len(msg_s) + len(ha_msg) +
                                  len(server['cluster_name']) + 3,
                                  "Standby", curses.color_pair(1))
                else:
                    ha_msg = ''

                string_at(win, 5,
                          len(ha_msg) + len(msg_s) + 2,
                          server['cluster_name'], curses.color_pair(1))
        else:
            string_at(win, 5, 0, "Uninitialized Vault", curses.color_pair(1))
    else:
        string_at(win, 4, 0,
                  'OFFLINE', curses.color_pair(3) | curses.A_BOLD, 'center')

    win.hline(win_y - 3, 1, curses.ACS_HLINE, win_x - 2)
    refresh_controls(win, server)
    win.refresh()


def refresh_controls(win, server):
    """Refresh our visible controls. This varies based on server state
    and authentication"""
    esc_msg = '[ESC] Quit'
    seal_msg = '[s]eal'
    unseal_msg = '[u]nseal'
    init_msg = '[i]nit'
    rekey_msg = '[r]ekey'
    cancel_msg = '[c]ancel'
    enter_msg = '[e]nter'
    rotate_msg = 'r[o]tate'
    regen_msg = "re[g]en"
    step_msg = 'ste[p]'

    line = win.getmaxyx()[0] - 2
    string_at(win, line, 0, esc_msg, curses.color_pair(1))
    next_x = len(esc_msg) + 2
    if 'init' in server:
        if not server['init']:
            string_at(win, line, next_x, init_msg, curses.color_pair(3))
        else:
            if server['sealed']:
                string_at(win, line, next_x, unseal_msg, curses.color_pair(1))
            else:
                if (not server['ha'] or (server['ha'] and server['leader'])) \
                   and (server['rekey'] or server['regenerating']):
                    string_at(win, line, next_x,
                              cancel_msg, curses.color_pair(7))
                    string_at(win, line,
                              next_x + len(cancel_msg) + 1,
                              enter_msg, curses.color_pair(1))
                else:
                    if server['is_root']:
                        if not server['ha'] or \
                           (server['ha'] and server['leader']):
                            string_at(win, line, next_x,
                                      seal_msg, curses.color_pair(3))
                            string_at(win, line,
                                      next_x + len(seal_msg) + 1,
                                      rekey_msg, curses.color_pair(1))
                            string_at(win, line,
                                      next_x + len(seal_msg) +
                                      len(rekey_msg) + 2,
                                      rotate_msg, curses.color_pair(1))
                            string_at(win, line,
                                      next_x + len(seal_msg) + len(rekey_msg) +
                                      len(rotate_msg) + 3,
                                      regen_msg, curses.color_pair(1))

                        if server['ha'] and server['leader']:
                            string_at(win, line,
                                      next_x + len(seal_msg) + len(rekey_msg) +
                                      len(rotate_msg) + len(regen_msg) + 4,
                                      step_msg, curses.color_pair(1))
                    else:
                        string_at(win, line, next_x,
                                  rekey_msg, curses.color_pair(1))
                        string_at(win, line,
                                  next_x + len(rekey_msg) + 2,
                                  regen_msg, curses.color_pair(1))


def do_init(screen, server):
    """GUI construct for initializing a blank Vault instance"""
    if not init(server):
        popup(screen, 'Unexpected return during vault init!')


def main_loop(screen):
    """Main interaction loop."""
    done = False
    selection = 0
    main_win = None
    while not done:
        main_win = refresh_screen(screen, selection, main_win)
        screen.timeout(500)
        ch = screen.getch()
        if ch == 27:
            screen.erase()
            screen.refresh()
            popup(screen, "Thank you for playing")
            curses.endwin()
            return
        elif ch == curses.KEY_DOWN:
            if selection < len(conf.get('vaults')) - 1:
                selection = selection + 1
        elif ch == curses.KEY_UP:
            if selection > 0:
                selection = selection - 1
        elif ch == 10:
            focus_loop(screen, selection)


def gui():
    """It's amazing what qualifies as a GUI these days"""
    update_servers()
    screen = init_screen()
    try:
        main_loop(screen)
        finish()
    except Exception:
        screen.erase()
        screen.move(1, 1)
        screen.refresh()
        curses.endwin()
        etype, val, trace = sys.exc_info()
        traceback.print_exception(etype, val, trace)
