#!/opt/dosis2/bin/python3-venv

import os
import sys
import readline
import string
import subprocess
from collections import OrderedDict

import enum
import psycopg2
import six.moves
from typing import Optional, Callable, Tuple, TypeVar, List, Union, Type

T = TypeVar('T')
readline.parse_and_bind('tab: complete')

def dell_pc_deprecation_warning(old_step, new_step):
    def _dmidecode(field):
        try:
            return subprocess.check_output(
                ["dmidecode", "-s", field],
                stderr=subprocess.DEVNULL,
                universal_newlines=True,
            ).strip().lower()
        except Exception:
            return ""

    manufacturer = _dmidecode("system-manufacturer")
    model = _dmidecode("system-product-name")

    # Detect newer Dell models
    new_model_markers = ("pro", "slim", "plus", "ultra")

    if any(marker in model for marker in new_model_markers):
        print()
        print("=" * 60)
        print("    DEPRECATED STEP ON NEW DELL PC MODEL")
        print()
        print("  This system appears to be a newer Dell model:")
        print(f"    Model: {model}")
        print()
        print(f'  On these PCs, "{old_step}" is deprecated.')
        print(f'  Please run "{new_step}" instead.')
        print("=" * 60)
        print()

        sys.exit(0)

dell_pc_deprecation_warning("step2", "step-b")


class SystemType(enum.Enum):
    L60 = (10, 'l60', 'l60')
    C60 = (11, 'c60', 'c60')
    U60 = (12, 'u60', 'u60')
    SITE_SERVER = (0, 'site-server', 'ss')


def generate_dafe_tag(system_type, serial_number, suffix=''):
    # type: (SystemType, int) -> str
    if system_type == SystemType.SITE_SERVER:
        return '%s%s%s' % (system_type.value[2].upper(), serial_number, suffix)
    else:
        return '%s_D%s%s' % (system_type.value[2].upper(), serial_number, suffix)


def get_database_add_dafe_function(system_type):
    # type: (SystemType) -> str
    if system_type == SystemType.L60:
        return 'select equipment.add_dafe_l60(%(serialnum)s)'
    elif system_type == SystemType.C60:
        return 'select equipment.add_dafe_c60(%(serialnum)s)'
    elif system_type == SystemType.U60:
        return 'select equipment.add_dafe_u60(%(serialnum)s)'
    else:
        raise Exception('no database function programmed for: %s' % (system_type,))


def convert_u_axis_to_427_lead(dafe_tag):
    # type: (str) -> None
    def __get_script():
        # type: () -> str
        return '/opt/dosis2/database/tools/.mfg_clean/uaxis_conversion_to_427.bash'

    if os.path.exists(__get_script()):
        subprocess.check_call([__get_script(), '-d', dafe_tag])
    else:
        raise OSError('%s not found' % (__get_script(),))


def setup_system(system_type, serial_number, setup_427_lead=False, dafe_tag_suffix=''):
    # type: (SystemType, int, bool, str) -> None
    subprocess.check_call([
        '/usr/share/dosis2/bin/setup-dosis-network',
        system_type.value[1].upper().replace('-', '_'),
        generate_dafe_tag(system_type, serial_number, suffix=dafe_tag_suffix)])

    if system_type != SystemType.SITE_SERVER:
        connection = psycopg2.connect(dbname='dosis2', user='dsuper')
        connection.autocommit = True
        try:
            with connection.cursor() as cursor:
                print(('Adding dafe to database %s' % generate_dafe_tag(
                    system_type,
                    serial_number,
                    suffix=dafe_tag_suffix)))

                cursor.execute(
                    get_database_add_dafe_function(system_type),
                    vars={'serialnum': serial_number})
        finally:
            connection.close()

        if setup_427_lead:
            print('Converting U-Axis to 0.427" lead')
            convert_u_axis_to_427_lead(generate_dafe_tag(system_type, serial_number, suffix=dafe_tag_suffix))

    # this is repeated from step1 to help with a bug that sometimes happens
    # in U60 units where the nic ports don't stay consistent. 
    try:
        print('Generating persistant network rules')
        subprocess.check_call(['/usr/share/dosis2/bin/generate-persistent-networks', '--output', '/etc/udev/rules.d/70-persistent-net.rules'])
    except subprocess.CalledProcessError as err:
        print('Something went wrong while trying to generate persistant network rules')

    try:
        subprocess.check_call(['/usr/share/dosis2/bin/setup-thirdparty'])
    except KeyboardInterrupt:
        pass


def enum_members(e):
    # type: (Type[enum.Enum]) -> OrderedDict
    return e.__members__


def request_value(header=None, prompt='> ', after_header_hook=None, validate_and_cast=lambda v: (True, '', '')):
    # type: (Optional[str], str, Optional[Callable[[], None]], Callable[[str], Tuple[bool, T, str]]) -> T
    while True:
        if header is not None:
            print(header)

        if callable(after_header_hook):
            after_header_hook()

        answer = six.moves.input(prompt)
        if callable(validate_and_cast):
            valid, value, reason = validate_and_cast(answer)
        else:
            valid, value, reason = True, answer, ''

        if valid:
            print('')
            return value
        else:
            print(('Invalid value [%s] %s' % (answer, reason)))
            print('')


def request_integer(header=None, prompt='> '):
    # type: (Optional[str], str) -> int
    def __validate_and_cast(value):
        # type: (str) -> (bool, int, str)
        try:
            return True, int(value), ''
        except ValueError:
            return False, value, 'cannot be interpreted as an integer'

    return request_value(
        header=header,
        prompt=prompt,
        validate_and_cast=__validate_and_cast)


def request_yes_no(header=None, prompt='(yes/no)? '):
    # type: (Optional[str], str) -> bool

    def __validate_and_cast(value):
        # type: (str) -> (bool, bool, str)
        return True, value in ('y', 'yes'), ''

    return request_value(
        header=header,
        prompt=prompt,
        validate_and_cast=__validate_and_cast)


def request_option_input(options, header=None, prompt='> '):
    # type: (List[Tuple[str, T]], Optional[str], str) -> T
    optionslookup = dict(options)

    def __validate_and_cast(value):
        # type: (str) -> (bool, Union[T, str], str)
        if value in optionslookup:
            return True, optionslookup[value], ''
        else:
            return False, value, 'must be one of %s' % ', '.join(list(optionslookup.keys()))

    def __print_options():
        for name, value in options:
            print(('  %s' % name))

    prevdelims = readline.get_completer_delims()
    completer = readline.get_completer()
    readline.set_completer_delims('')
    readline.set_completer(lambda text, state: [op for op, _ in options if op.startswith(text)][state])
    answer = request_value(
        header=header,
        prompt=prompt,
        after_header_hook=__print_options,
        validate_and_cast=__validate_and_cast)
    readline.set_completer(completer)
    readline.set_completer_delims(prevdelims)
    return answer


def run():
    dafe_types = [(v.value[1], v) for v in sorted(list(enum_members(SystemType).values()), key=lambda v: v.value[0])]

    dafe_type = request_option_input(dafe_types, header='Choose A Dafe Type:')  # type: SystemType
    dafe_serial_number = request_integer(prompt='%s\'s serial number? ' % dafe_type.value[1])

    if dafe_type == SystemType.SITE_SERVER:
        def __validate_and_cast_letter(text):
            # type: (str) -> (bool, str, str)
            if len(text) == 1 and text in string.ascii_uppercase:
                return True, text, ''
            else:
                return False, text, 'Must be a single uppercase letter'

        is_redundant_site_server = request_yes_no(prompt='Is this a redundant site server (yes/no)? ')

        if is_redundant_site_server:
            redundant_site_server_letter = request_value(
                prompt='Redundant site server letter (A-Z)? ',
                validate_and_cast=__validate_and_cast_letter)
        else:
            redundant_site_server_letter = ''

        print(('%s Configuration:' % generate_dafe_tag(
            dafe_type,
            dafe_serial_number,
            suffix=redundant_site_server_letter)))
        print(('  redundant=%s' % ('yes' if is_redundant_site_server else 'no')))
        print(('  letter=%s' % (redundant_site_server_letter if redundant_site_server_letter else 'N/A')))
        print('')
        if request_yes_no(prompt='Continue with configuration (yes/no)? '):
            setup_system(
                dafe_type,
                dafe_serial_number,
                setup_427_lead=False,
                dafe_tag_suffix=redundant_site_server_letter)
        else:
            print('Not configuring')
    else:
        use_427_lead = request_yes_no(prompt='Does the U-Axis have a 0.427 lead (yes/no)? ')
        print(('%s Configuration:' % generate_dafe_tag(dafe_type, dafe_serial_number)))
        print(('  u-axis 0.427 lead=%s' % ('yes' if use_427_lead else 'no')))
        print('')
        if request_yes_no(prompt='Continue with configuration (yes/no)? '):
            setup_system(dafe_type, dafe_serial_number, setup_427_lead=use_427_lead)
        else:
            print('Not configuring')


if __name__ == '__main__':
    run()
