from pytest import fixture, raises
from pathlib import Path
from os import getcwd, listdir, environ, makedirs
from os.path import isfile, join, isdir, dirname
from shutil import rmtree, copyfile
import logging

environ['TIRAMISU_LOCALE'] = 'en'

from rougail import Rougail, RougailConfig
from rougail.error import DictConsistencyError
from .custom import CustomOption


logger = logging.getLogger()
logger.setLevel(logging.INFO)

dico_dirs = Path('../rougail-tests/structures')
errors_dirs = Path('tests/errors')
rougail_test_dirs = Path('tests/dictionaries')


# if test_3_template.py failed, this temporary directory must be removed
tmp_dir = join(dico_dirs, 'tmp')
if isdir(tmp_dir):
    rmtree(tmp_dir)


test_ok = set()
test_raise = set()


def get_rougail_tests_dir(original_dir):
    pass


for test in dico_dirs.iterdir():
    if test.is_dir():
        test_ok.add(test.name)

for test in errors_dirs.iterdir():
    if test.is_dir():
        test_raise.add(test.name)

excludes = set([
#    '80family_several',
])
test_ok -= excludes
test_raise -= excludes
# test_ok = ['00_9default_calculation_information']
#test_ok = []
# test_raise = ['80unknown_default_variable_inside_dynamic_family']
#test_raise = []
test_multi = True
#test_multi = False
test_base = test_multi
#test_base = True
#test_base = False
test_no_namespace = test_multi
#test_no_namespace = True
#test_no_namespace = False

ORI_DIR = getcwd()

debug = False
#debug = True

test_ok = list(test_ok)
test_raise = list(test_raise)
test_ok.sort()
test_raise.sort()


@fixture(scope="module", params=test_ok)
def test_dir(request):
    return request.param


@fixture(scope="module", params=test_raise)
def test_dir_error(request):
    return request.param


def get_tiramisu_filename(test_dir, subdir, multi, namespace):
    if not namespace and not multi:
        filename = 'no_namespace.py'
    elif not multi:
        filename = 'base.py'
    else:
        filename = 'multi.py'
    return str(rougail_test_dirs / Path(test_dir).name /  subdir / filename)


def load_rougail_object(test_dir, rougailconfig, multi=False, namespace=False):
    rougailconfig['functions_file'] = [join('../rougail-tests/funcs/test.py')]
    dirs = [join(test_dir, 'rougail')]
    subfolder = join(test_dir, 'rougail2')
    if isdir(subfolder):
        dirs.append(subfolder)
    rougailconfig['main_structural_directories'] = dirs
    extra_namespaces = {}
    extras = listdir(join(test_dir))
    extras.sort()
    for extra in extras:
        if extra in ['rougail', 'rougail2']:
            continue
        subfolder = join(test_dir, extra)
        if isdir(subfolder):
            extra_namespaces[extra] = [subfolder]
    if extra_namespaces:
        if namespace is False and not multi:
            return None
        rougailconfig['extra_namespaces'] = extra_namespaces
    try:
        rougailconfig['tiramisu_cache'] = get_tiramisu_filename(test_dir, 'tmp', multi, namespace)
    except:
        rougailconfig['cli.tiramisu_cache'] = get_tiramisu_filename(test_dir, 'tmp', multi, namespace)
    rougailconfig['custom_types']['custom'] = CustomOption
    return Rougail(rougailconfig)


def save(test_dir, eolobj, multi=False, namespace=False, error=False):
    tiramisu_tmp = get_tiramisu_filename(test_dir, 'tmp', multi, namespace)
    tiramisu_tmp_dir = dirname(tiramisu_tmp)
    if isdir(tiramisu_tmp_dir):
        rmtree(tiramisu_tmp_dir)
    makedirs(tiramisu_tmp_dir)
    eolobj.run()
    tiramisu_file = get_tiramisu_filename(test_dir, 'tiramisu', multi, namespace)
    tiramisu_dir = dirname(tiramisu_file)
    if not error:
        if not isdir(tiramisu_dir):
            makedirs(tiramisu_dir)
        if not isfile(tiramisu_file) or debug:
            copyfile(tiramisu_tmp, tiramisu_file)
        with open(tiramisu_tmp, 'r') as fh:
            tiramisu_objects = fh.read()
        with open(tiramisu_file, 'r') as fh:
            tiramisu_objects_ori = fh.read()
        assert tiramisu_objects == tiramisu_objects_ori, f'{test_dir} -> {tiramisu_file}'
        if isdir(tiramisu_tmp_dir):
            rmtree(tiramisu_tmp_dir)


def test_structural_file1(test_dir):
    if not test_no_namespace:
        print('NAMESPACE!')
        return
    assert getcwd() == ORI_DIR
    test_dir_ = str(rougail_test_dirs / test_dir)
    rougailconfig = RougailConfig.copy()
    rougailconfig['main_namespace'] = None
    if (dico_dirs / test_dir / 'force_namespace').is_file():
        return
    if (dico_dirs / test_dir / 'default_structural_format_version').is_file():
        rougailconfig['default_structural_format_version'] = "1.1"
    else:
        rougailconfig['default_structural_format_version'] = None
    eolobj = load_rougail_object(str(dico_dirs / test_dir), rougailconfig)
    if not eolobj:
        return
    save(test_dir_, eolobj)
    assert getcwd() == ORI_DIR


def test_structural_file_namespace(test_dir):
    if not test_base:
        print('BASE!')
        return
    assert getcwd() == ORI_DIR
    test_dir_ = join(dico_dirs, test_dir)
    rougailconfig = RougailConfig.copy()
    rougailconfig['main_namespace'] = 'Rougail'
    if (dico_dirs / test_dir / 'force_no_namespace').is_file():
        return
    if (dico_dirs / test_dir / 'default_structural_format_version').is_file():
        rougailconfig['default_structural_format_version'] = "1.1"
    else:
        rougailconfig['default_structural_format_version'] = None
    eolobj = load_rougail_object(test_dir_, rougailconfig, namespace=True)
    if not eolobj:
        return
    save(test_dir_, eolobj, namespace=True)
    assert getcwd() == ORI_DIR


def test_error_structural_file_namespace(test_dir_error):
    assert getcwd() == ORI_DIR
    test_dir_ = join(errors_dirs, test_dir_error)
    errno = []
    rougailconfig = RougailConfig.copy()
    rougailconfig['main_namespace'] = 'Rougail'
    eolobj = load_rougail_object(test_dir_, rougailconfig, namespace=True)
    if eolobj is None:
        return
    for i in listdir(test_dir_):
        if i.startswith('errno_'):
            errno.append(int(i.split('_')[1]))
    if not errno:
        errno.append(0)
    with raises(DictConsistencyError) as err:
        save(test_dir_, eolobj, error=True)
    assert err.value.errno in errno, f'expected errno: {errno}, errno: {err.value.errno}, msg: {err}'
    tiramisu_tmp_dir = dirname(get_tiramisu_filename(test_dir_, 'tmp', False, True))
    if isdir(tiramisu_tmp_dir):
        rmtree(tiramisu_tmp_dir)
    assert getcwd() == ORI_DIR


def test_error_structural_file(test_dir_error):
    assert getcwd() == ORI_DIR
    test_dir_ = join(errors_dirs, test_dir_error)
    if isfile(join(test_dir_, 'force_namespace')):
        return
    errno = []
    rougailconfig = RougailConfig.copy()
    rougailconfig['main_namespace'] = None
    eolobj = load_rougail_object(test_dir_, rougailconfig, namespace=False)
    if eolobj is None:
        return
    for i in listdir(test_dir_):
        if i.startswith('errno_'):
            errno.append(int(i.split('_')[1]))
    if not errno:
        errno.append(0)
    with raises(DictConsistencyError) as err:
        save(test_dir_, eolobj, error=True)
    assert err.value.errno in errno, f'expected errno: {errno}, errno: {err.value.errno}, msg: {err}'
    tiramisu_tmp_dir = dirname(get_tiramisu_filename(test_dir_, 'tmp', False, True))
    if isdir(tiramisu_tmp_dir):
        rmtree(tiramisu_tmp_dir)
    assert getcwd() == ORI_DIR
