import itertools
import os
import subprocess
import sys

# Require Python 3.7+ for ordered dictionaries so that the order of the
# generated tests remain the same.
if sys.version_info[:2] < (3, 7):
    print('ERROR: This script requires Python >= 3.7, not:')
    print(sys.version)
    print('Usage: python3 %s' % (sys.argv[0]))
    exit(1)


dirname = os.path.dirname
DIR = dirname(os.path.realpath(__file__))
SOURCE = dirname(dirname(dirname(DIR)))


def template(filename):
    fullpath = os.path.join(DIR, filename)
    with open(fullpath, 'r') as f:
        return f.read()


def write_test(filename, data):
    fullpath = os.path.join(DIR, filename + '.yml')
    with open(fullpath, 'w') as f:
        f.write(data)

    print(f"Generated {fullpath}")


# Maps from error_name to (error_code,)
ERR_CODES = {
    'InterruptedAtShutdown': (11600,),
    'InterruptedDueToReplStateChange': (11602,),
    'NotMasterOrSecondary': (13436,),
    'PrimarySteppedDown': (189,),
    'ShutdownInProgress': (91,),
    'NotMaster': (10107,),
    'NotMasterNoSlaveOk': (13435,),
}


def create_stale_tests():
    tmp = template('stale-topologyVersion.yml.template')
    for error_name in ERR_CODES:
        test_name = f'stale-topologyVersion-{error_name}'
        error_code, = ERR_CODES[error_name]
        data = tmp.format(**locals())
        write_test(test_name, data)

TV_GREATER = '''
      topologyVersion:
        processId:
          "$oid": '000000000000000000000001'
        counter:
          "$numberLong": "2"'''
TV_GREATER_FINAL =  '''
          processId:
            "$oid": '000000000000000000000001'
          counter:
            "$numberLong": "2"'''
TV_CHANGED = '''
      topologyVersion:
        processId:
          "$oid": '000000000000000000000002'
        counter:
          "$numberLong": "1"'''
TV_CHANGED_FINAL =  '''
          processId:
            "$oid": '000000000000000000000002'
          counter:
            "$numberLong": "1"'''

# Maps non-stale error description to:
# (error_topology_version, final_topology_version)
NON_STALE_CASES = {
    'topologyVersion missing': ('', ' null'),
    'topologyVersion greater': (TV_GREATER, TV_GREATER_FINAL),
    'topologyVersion proccessId changed': (TV_CHANGED, TV_CHANGED_FINAL),
}


def create_non_stale_tests():
    tmp = template('non-stale-topologyVersion.yml.template')
    for error_name, description in itertools.product(
            ERR_CODES, NON_STALE_CASES):
        test_name = f'non-stale-{description.replace(" ", "-")}-{error_name}'
        error_code, = ERR_CODES[error_name]
        error_topology_version, final_topology_version = NON_STALE_CASES[description]
        # On 4.2+, only ShutdownInProgress and InterruptedAtShutdown will
        # clear the pool.
        if error_name in ("ShutdownInProgress", "InterruptedAtShutdown"):
            final_pool_generation = 1
        else:
            final_pool_generation = 0

        data = tmp.format(**locals())
        write_test(test_name, data)


WHEN = ['beforeHandshakeCompletes', 'afterHandshakeCompletes']
STALE_GENERATION_COMMAND_ERROR = '''
    type: command
    response:
      ok: 0
      errmsg: {error_name}
      code: {error_code}
      topologyVersion:
        processId:
          "$oid": '000000000000000000000001'
        counter:
          "$numberLong": "2"'''
STALE_GENERATION_NETWORK_ERROR = '''
    type: {network_error_type}'''


def create_stale_generation_tests():
    tmp = template('stale-generation.yml.template')
    # Stale command errors
    for error_name, when in itertools.product(ERR_CODES, WHEN):
        test_name = f'stale-generation-{when}-{error_name}'
        error_code, = ERR_CODES[error_name]
        stale_error = STALE_GENERATION_COMMAND_ERROR.format(**locals())
        data = tmp.format(**locals())
        write_test(test_name, data)
    # Stale network errors
    for network_error_type, when in itertools.product(
            ['network', 'timeout'], WHEN):
        test_name = f'stale-generation-{when}-{network_error_type}'
        stale_error = STALE_GENERATION_NETWORK_ERROR.format(**locals())
        data = tmp.format(**locals())
        write_test(test_name, data)


def create_pre_42_tests():
    tmp = template('pre-42.yml.template')
    # All "not master"/"node is recovering" clear the pool on <4.2
    for error_name in ERR_CODES:
        test_name = f'pre-42-{error_name}'
        error_code, = ERR_CODES[error_name]
        data = tmp.format(**locals())
        write_test(test_name, data)


def create_post_42_tests():
    tmp = template('post-42.yml.template')
    for error_name in ERR_CODES:
        test_name = f'post-42-{error_name}'
        error_code, = ERR_CODES[error_name]
        # On 4.2+, only ShutdownInProgress and InterruptedAtShutdown will
        # clear the pool.
        if error_name in ("ShutdownInProgress", "InterruptedAtShutdown"):
            final_pool_generation = 1
        else:
            final_pool_generation = 0
        data = tmp.format(**locals())
        write_test(test_name, data)


create_stale_tests()
create_non_stale_tests()
create_stale_generation_tests()
create_pre_42_tests()
create_post_42_tests()

print('Running make')
subprocess.run(f'cd {SOURCE} && make', shell=True, check=True)
