import os
from pytest import fixture  # , raises
from pathlib import Path
from yaml import safe_load
from shutil import copytree, rmtree
from filecmp import dircmp

from rougail import Rougail
from rougail.user_data import mandatories
from rougail.output_ansible import RougailOutputAnsible as RougailOutput
from rougail.user_data_yaml import RougailUserDataYaml

from rougail_tests.utils import get_structures_list, get_rougail_config, get_values_for_config


WITH_DONE = False
# WITH_DONE = True
HERE = Path(__file__).parent


EXT = 'json'

os.environ['COLUMNS'] = '80'


excludes = [
#    '04_5disabled_calculation_variable11',
#    '60_5family_dynamic_variable_outside_sub_suffix',
]

if WITH_DONE:
    test_ok = get_structures_list(excludes, HERE)
else:
    test_ok = get_structures_list(excludes)
# test_ok = [Path('../rougail-tests/structures/00_1empty_variable')]


def idfn(fixture_value):
    return fixture_value.name


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


def _test_structural_files(test_dir, namespace, ext, *, read_write=True, mandatory=False, do_calc=True):
    for output in ["inventory", 'doc']:
        rougailconfig = get_rougail_config(test_dir, namespace)
        if not rougailconfig:
            continue
        ##################################
        rougailconfig['step.output'] = 'ansible'
        rougailconfig['ansible.output'] = output
#        rougailconfig["json.mandatory"] = mandatory
        if output == 'inventory':
            extra_namespaces = rougailconfig["extra_namespaces"]
            extra_namespaces["hosts"] = [str(HERE / "hosts")]
            rougailconfig['extra_namespaces'] = extra_namespaces
        ##################################
        dir_name = 'test'
        if namespace:
            dir_name += '_namespace'
        elif (test_dir / 'force_namespace').is_file():
            return
        ##################################
        if read_write:
            dir_name += '_read_write'
        if mandatory:
            dir_name += '_mandatory'
        if not do_calc:
            dir_name += '_errors'
        if output == 'inventory':
            dirname = 'results'
        else:
            dirname = 'results-doc'
            ext = 'sh'
        output_root = HERE / dirname / dir_name
        output_file = output_root / (test_dir.name + "." + ext)
        ##################################
        rougail = Rougail(rougailconfig)
        config = rougail.run()
        config.information.set("description_type", "path_and_description")
        ##################################
        if do_calc and (mandatory or not read_write):
            if mandatory:
                get_values_for_config(config, exclude_namespace="hosts")
            else:
                get_values_for_config(config, level="mandatories", exclude_namespace="hosts")
        ##################################
        if read_write:
            config.property.read_write()
        else:
            config.property.read_only()
        errors = []
        if mandatory:
            mandatories(config, errors)
        if output != 'inventory':
            subconfig = config.list()
            if len(subconfig) != 1 or not subconfig[0].isoptiondescription():
                continue
        if output == 'doc':
            test_ansible_dir = Path("tmp")
            if test_ansible_dir.is_dir():
                rmtree(test_ansible_dir)
            test_ansible_dir.mkdir()
            rougailconfig["ansible.doc.project_name"] = "rougail"
            rougailconfig["ansible.doc.author"] = "rougail"
            rougailconfig["ansible.doc.root_directory"] = str(test_ansible_dir)
            copytree("tests/playbooks", test_ansible_dir / "playbooks")
        generated_output = RougailOutput(config, rougailconfig=rougailconfig, user_data_errors=errors).run()[1]
        if not output_file.is_file():
            if not output_file.parent.is_dir():
                output_file.parent.mkdir(parents=True)
            with output_file.open('w') as outfh:
                outfh.write(generated_output)
        with output_file.open() as outfh:
             attented_output = outfh.read()
        assert generated_output == attented_output, f'filename {output_file}'
        if output == 'doc':
            rmtree(test_ansible_dir / "playbooks")
            ansible_dir = output_root / (test_dir.name + ".ansible")
            if not ansible_dir.is_dir():
                copytree(test_ansible_dir, ansible_dir)
            def _is_diff(dcmp):
                found = False
                for name in dcmp.diff_files:
                    found = True
                    print("diff_file %s found in %s and %s" % (name, dcmp.left,
                          dcmp.right))
                for sub_dcmp in dcmp.subdirs.values():
                    if _is_diff(sub_dcmp):
                        found = True
                return found

            def is_diff(src, dst):
                src_files = {i.relative_to(src) for i in src.glob("**")}
                dst_files = {i.relative_to(dst) for i in dst.glob("**")}
                if src_files - dst_files or dst_files - src_files:
                    print("files not found is source", src_files - dst_files)
                    print("files not found is dest", dst_files - src_files)
                    return True
                dcmp = dircmp(src, dst)
                return _is_diff(dcmp)
            assert is_diff(test_ansible_dir, ansible_dir) is False
            rmtree(test_ansible_dir)
    if WITH_DONE:
        with (HERE / (test_dir.name + ".DONE")).open("w") as fh:
            fh.write('')


# do not test without namespace, ansible needs namespaces
def test_structural_files_ansible_namespace(test_dir):
    _test_structural_files(test_dir, True, EXT)


def test_structural_files_ansible_namespace_errors(test_dir):
    _test_structural_files(test_dir, True, EXT, do_calc=False, mandatory=True)


def test_structural_files_ansible_namespace_read_only(test_dir):
    _test_structural_files(test_dir, True, EXT, read_write=False)


def test_structural_files_ansible_namespace_mandatory(test_dir):
    _test_structural_files(test_dir, True, EXT, mandatory=True)


def test_structural_files_ansible_namespace_mandatory_read_only(test_dir):
    _test_structural_files(test_dir, True, EXT, read_write=False, mandatory=True)


def test_warnings():
    rougailconfig = get_rougail_config(Path('tests/warnings/structures'), True)
    ##################################
    rougailconfig['step.output'] = 'ansible'
    rougailconfig['step.user_data'] = ['yaml']
    rougailconfig['yaml.filename'] = ['tests/warnings/yaml/config.yml']
    extra_namespaces = rougailconfig["extra_namespaces"]
    extra_namespaces["hosts"] = [str(HERE / "hosts")]
    rougailconfig['extra_namespaces'] = extra_namespaces
    ##################################
    rougail = Rougail(rougailconfig)
    config = rougail.run()
    generated_user_data = RougailUserDataYaml(config, rougailconfig=rougailconfig).run()
    err_warn = rougail.user_data(generated_user_data)
    output = RougailOutput(
        config=config,
        rougailconfig=rougailconfig,
        user_data_errors=err_warn["errors"],
        user_data_warnings=err_warn["warnings"],
    )
    ret = output.run()
    assert ret[0] is True
    assert safe_load(ret[1]) == {
  "_meta": {
    "hostvars": {
      "localhost": {
        "_warnings": [
          "rougail: variable or family \"an_unknown_var\" does not exist so cannot load \"rougail.an_unknown_var\", it will be ignored when loading from the YAML file \"tests/warnings/yaml/config.yml\""
        ]
      },
      "GROUP1_01": {
        "ansible_host": "group1.net",
        "rougail": {
          "a_var": "a_value"
        }
      },
      "GROUP2_01": {
        "ansible_host": "group2.net",
        'rougail': {
           'a_var': 'a_value',
         },
       },
       'GROUP2_02': {
           'ansible_host': 'group3.net',
           'rougail': {
               'a_var': 'a_value',
           },
       },
       'group4.net': {
           'ansible_host': 'group4.net',
           'rougail': {
               'a_var': 'a_value',
           },
       },
       'group5.net': {
           'ansible_host': 'group5.net',
        "rougail": {
          "a_var": "a_value"
        }
      }
    }
  },
  "ungrouped": {
    "hosts": [
      "localhost"
    ]
  },
  "group1": {
    "hosts": [
      "GROUP1_01"
    ]
  },
  "group2": {
    "hosts": [
      "GROUP2_01",
      'GROUP2_02'
    ]
  },

  'group3': {
      'hosts': [
          'group4.net',
          'group5.net',
    ]
  },

  "groups": {
    "children": [
      "group1",
      "group2"
    ]
  }
}


def test_no_warnings():
    rougailconfig = get_rougail_config(Path('tests/warnings/structures'), True)
    ##################################
    rougailconfig['step.output'] = 'ansible'
    rougailconfig['ansible.inventory.export_warnings'] = False
    rougailconfig['step.user_data'] = ['yaml']
    rougailconfig['yaml.filename'] = ['tests/warnings/yaml/config.yml']
    extra_namespaces = rougailconfig["extra_namespaces"]
    extra_namespaces["hosts"] = [str(HERE / "hosts")]
    rougailconfig['extra_namespaces'] = extra_namespaces
    ##################################
    rougail = Rougail(rougailconfig)
    config = rougail.run()
    generated_user_data = RougailUserDataYaml(config, rougailconfig=rougailconfig).run()
    err_warn = rougail.user_data(generated_user_data)
    output = RougailOutput(
        config=config,
        rougailconfig=rougailconfig,
        user_data_errors=err_warn["errors"],
        user_data_warnings=err_warn["warnings"],
    )
    ret = output.run()
    assert ret[0] is True
    assert safe_load(ret[1]) == {
  "_meta": {
    "hostvars": {
      "GROUP1_01": {
        "ansible_host": "group1.net",
        "rougail": {
          "a_var": "a_value"
        }
      },
      "GROUP2_01": {
        "ansible_host": "group2.net",
        'rougail': {
           'a_var': 'a_value',
         },
       },
       'GROUP2_02': {
           'ansible_host': 'group3.net',
           'rougail': {
               'a_var': 'a_value',
           },
       },
       'group4.net': {
           'ansible_host': 'group4.net',
           'rougail': {
               'a_var': 'a_value',
           },
       },
       'group5.net': {
           'ansible_host': 'group5.net',
        "rougail": {
          "a_var": "a_value"
        }
      }
    }
  },
  'group1': {
      'hosts': [
          'GROUP1_01',
      ],
  },
  'group2': {
      'hosts': [
          'GROUP2_01',
          'GROUP2_02',
      ],
  },
  'group3': {
      'hosts': [
          'group4.net',
          'group5.net',
      ],
  },
  'groups': {
      'children': [
          'group1',
          'group2',
      ],
  }
}


def test_host_with_vars():
    rougailconfig = get_rougail_config(Path('tests/warnings/structures'), True)
    ##################################
    rougailconfig['step.output'] = 'ansible'
    rougailconfig['ansible.inventory.export_warnings'] = False
    rougailconfig['step.user_data'] = ['yaml']
    rougailconfig['yaml.filename'] = ['tests/warnings/yaml/config.yml']
    extra_namespaces = rougailconfig["extra_namespaces"]
    extra_namespaces["hosts"] = [str(HERE / "hosts-with-vars")]
    rougailconfig['extra_namespaces'] = extra_namespaces
    ##################################
    rougail = Rougail(rougailconfig)
    config = rougail.run()
    generated_user_data = RougailUserDataYaml(config, rougailconfig=rougailconfig).run()
    err_warn = rougail.user_data(generated_user_data)
    output = RougailOutput(
        config=config,
        rougailconfig=rougailconfig,
        user_data_errors=err_warn["errors"],
        user_data_warnings=err_warn["warnings"],
    )
    ret = output.run()
    assert ret[0] is True
    assert safe_load(ret[1]) == {
  "_meta": {
    "hostvars": {
      "GROUP1_01": {
        "ansible_host": "group1.net",
        "rougail": {
          "a_var": "a_value"
        }
      },
      "GROUP2_01": {
        'a_family': {
            'with_a_variable': 1,
        },
        'added_var1': True,
        'added_var2': True,
        "ansible_host": "group2.net",
        'rougail': {
           'a_var': 'a_value',
         },
       },
       'GROUP2_02': {
           'a_family': {
               'with_a_variable': 1,
           },
           'added_var1': True,
           'ansible_host': 'group3.net',
           'rougail': {
               'a_var': 'a_value',
           },
       },
       'group4.net': {
           'ansible_host': 'group4.net',
           'rougail': {
               'a_var': 'a_value',
           },
       },
       'group5.net': {
           'ansible_host': 'group5.net',
        "rougail": {
          "a_var": "a_value"
        }
      }
    }
  },
  'group1': {
      'hosts': [
          'GROUP1_01',
      ],
  },
  'group2': {
      'hosts': [
          'GROUP2_01',
          'GROUP2_02',
      ],
  },
  'group3': {
      'hosts': [
          'group4.net',
          'group5.net',
      ],
  },
  'groups': {
      'children': [
          'group1',
          'group2',
      ],
  }
}
