#
# Copyright The NOMAD Authors.
#
# This file is part of NOMAD. See https://nomad-lab.eu for further info.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
"""
Tests for nyaml2nxdl tool
"""

import filecmp
import os
import re
import yaml
import sys
from datetime import datetime
from pathlib import Path
from collections import OrderedDict

import lxml.etree as ET
import pytest
from click.testing import CliRunner

from nyaml import cli as nyaml2nxdl
from nyaml import nyaml2nxdl as nyaml2nxdl_forward_tools
from nyaml.comment_collector import CommentCollector
from nyaml.helper import LineLoader, remove_namespace_from_tag
from nyaml.nyaml2nxdl import get_nxdl_copyright_license, handle_each_part_doc

LATEST_COPYRIGHT_YEAR = f"{datetime.now().year}-{datetime.now().year}"
LATEST_COPYRIGHT = rf"# Copyright \(C\) {LATEST_COPYRIGHT_YEAR} NeXus International Advisory Committee \(NIAC\)"
COPYRIGHT_REPLACEMENT = (
    f"# Copyright (C) 2010-2020 NeXus International Advisory Committee (NIAC)"
)


def check_and_replace_latest_copyright(nxdl_file):
    """Check if the latest copyright date has been written properly,
    and replace it accordingly to the test data file.
    """
    content = nxdl_file.read_text()
    generated_copyright = re.findall(LATEST_COPYRIGHT, content, re.DOTALL)
    assert len(generated_copyright) == 1, (
        f"No copyright or not correct copyright year found in {nxdl_file}"
    )
    content = re.sub(LATEST_COPYRIGHT, COPYRIGHT_REPLACEMENT, content)
    nxdl_file.write_text(content)


def delete_duplicates(list_of_matching_string):
    """
    Delete duplicate from lists
    """
    return list(dict.fromkeys(list_of_matching_string))


def check_file_fresh_baked(test_file):
    """
    Get sure that the test file is generated by the converter
    """
    path = Path(test_file)
    timestamp = datetime.fromtimestamp(path.stat().st_mtime).strftime("%d/%m/%Y %H:%M")
    now = datetime.now().strftime("%d/%m/%Y %H:%M")
    assert timestamp == now, "xml file not generated"


def find_matches(xml_file, desired_matches):
    """
        Read xml file and find desired matches. Return a list of two lists in the form:
    [[matching_line],[matching_line_index]]
    """
    with open(xml_file, "r", encoding="utf-8") as file:
        xml_reference = file.readlines()
    lines = []
    lines_index = []
    found_matches = []
    for i, line in enumerate(xml_reference):
        for desired_match in desired_matches:
            if str(desired_match) in str(line):
                lines.append(line)
                lines_index.append(i)
                found_matches.append(desired_match)
    # ascertain that all the desired matches were found in file
    found_matches_clean = delete_duplicates(found_matches)
    assert len(found_matches_clean) == len(desired_matches), (
        "some desired_matches were \nnot found in file"
    )
    return [lines, lines_index]


def compare_matches(ref_xml_file, test_yml_file, test_xml_file, desired_matches):
    """
        Check if a new xml file is generated
    and if test xml file is equal to reference xml file
    """
    # Reference file is read
    ref_matches = find_matches(ref_xml_file, desired_matches)
    # Test file is generated
    runner = CliRunner()
    result = runner.invoke(nyaml2nxdl.launch_tool, [test_yml_file])
    assert result.exit_code == 0
    check_file_fresh_baked(test_xml_file)
    # Test file is read
    test_matches = find_matches(test_xml_file, desired_matches)
    assert test_matches == ref_matches


def compare_yaml_content(yaml_dict1, yaml_dict2, test_key_list):
    for k_val1, k_val2 in zip(yaml_dict1.items(), yaml_dict2.items()):
        key1, val1 = k_val1
        key2, val2 = k_val2
        for ref_key in test_key_list:
            if key1 == ref_key and key2 == ref_key:
                assert val1 == val2, f"{ref_key} content is not the same."
            elif isinstance(val1, dict) and isinstance(val2, dict):
                compare_yaml_content(val1, val2, test_key_list)


def test_links():
    """
    Check the correct parsing of links
    """
    ref_xml_link_file = "tests/data/Ref_NXtest_links.nxdl.xml"
    test_yml_link_file = "tests/data/NXtest_links.yaml"
    test_xml_link_file = "tests/data/NXtest_links.nxdl.xml"
    # ref_xml_link_file = os.path.abspath(data_path + '/Ref_NXtest_links.nxdl.xml')
    # test_yml_link_file = os.path.abspath(data_path + '/NXtest_links.yaml')
    # test_xml_link_file = os.path.abspath(data_path + '/NXtest_links.nxdl.xml')
    desired_matches = ["<link", "/>"]
    compare_matches(
        ref_xml_link_file, test_yml_link_file, test_xml_link_file, desired_matches
    )
    os.remove("tests/data/NXtest_links.nxdl.xml")
    sys.stdout.write("Test on links okay.\n")


def test_nametypes_nyaml2nxdl():
    """
    Check the correct handling of the nameType attribute for the direction
    nyaml->nxdl.
    """
    ref_xml_file = "tests/data/yaml2nxdl/ref_allowed_nameType.nxdl.xml"
    test_yml_file = "tests/data/yaml2nxdl/allowed_nameType.yaml"
    test_xml_file = "tests/data/yaml2nxdl/allowed_nameType.nxdl.xml"
    desired_matches = ["partial", "specified", "any"]

    compare_matches(ref_xml_file, test_yml_file, test_xml_file, desired_matches)
    os.remove(test_xml_file)
    sys.stdout.write("Test on open/closed enumerations okay.\n")


@pytest.mark.parametrize(
    "input_tuple, exit_code, expected_error",
    [
        # Wrong name type
        (
            ("groupGROUP(NXobject)", "my_name_type"),
            1,
            'Name "groupGROUP" has nameType="my_name_type", but only one of ("specified", "any", "partial") is allowed.',
        ),
        (
            ("my_field", "my_name_type"),
            1,
            'Name "my_field" has nameType="my_name_type", but only one of ("specified", "any", "partial") is allowed.',
        ),
        (
            (r"\@MYattribute", "my_name_type"),
            1,
            r'Name "\@MYattribute" has nameType="my_name_type", but only one of ("specified", "any", "partial") is allowed.',
        ),
        (
            ("link(link)", "my_name_type"),
            1,
            'Name "link(link)" has nameType="my_name_type", but only one of ("specified", "any", "partial") is allowed.',
        ),
        # Unnamed groups
        (
            ("(NXobject)", "specified"),
            1,
            'Unnamed group should have either no nameType or nameType="any". Found nameType="specified".',
        ),
        (
            ("(NXobject)", "partial"),
            1,
            'Unnamed group should have either no nameType or nameType="any". Found nameType="partial".',
        ),
        # Lower case names
        (
            ("lower_case_group_any(NXobject)", "any"),
            0,
            'Warning: Name "lower_case_group_any" (all lowercase) has nameType="any", which makes it fully renameable. Is that intentional?',
        ),
        (
            ("lower_case_field_any", "any"),
            0,
            'Warning: Name "lower_case_field_any" (all lowercase) has nameType="any", which makes it fully renameable. Is that intentional?',
        ),
        (
            (r"\@lower_case_attribute_any", "any"),
            0,
            r'Warning: Name "\@lower_case_attribute_any" (all lowercase) has nameType="any", which makes it fully renameable. Is that intentional?',
        ),
        (
            ("lower_case_link_any(link)", "any"),
            0,
            'Warning: Name "lower_case_link_any(link)" (all lowercase) has nameType="any", which makes it fully renameable. Is that intentional?',
        ),
        (
            ("lower_case_group_partial(NXobject)", "partial"),
            0,
            'Error: Name "lower_case_group_partial" (all lowercase) has nameType="partial", but nothing can be replaced. Consider introducing upper case letters or dropping nameType="partial".',
        ),
        (
            ("lower_case_field_partial", "partial"),
            0,
            'Error: Name "lower_case_field_partial" (all lowercase) has nameType="partial", but nothing can be replaced. Consider introducing upper case letters or dropping nameType="partial".',
        ),
        (
            (r"\@lower_case_attribute_partial:", "partial"),
            0,
            r'Error: Name "\@lower_case_attribute_partial:" (all lowercase) has nameType="partial", but nothing can be replaced. Consider introducing upper case letters or dropping nameType="partial".',
        ),
        (
            ("lower_case_link_any(link)", "partial"),
            0,
            'Error: Name "lower_case_link_any(link)" (all lowercase) has nameType="partial", but nothing can be replaced. Consider introducing upper case letters or dropping nameType="partial".',
        ),
        # Upper case names
        (
            ("OBJECT(NXobject)", "partial"),
            0,
            'Warning: Name "OBJECT" (all uppercase) has nameType="partial". Since the name only has uppercase letters, there is no difference to nameType="any".',
        ),
        (
            ("FIELD", "partial"),
            0,
            'Warning: Name "FIELD" (all uppercase) has nameType="partial". Since the name only has uppercase letters, there is no difference to nameType="any".',
        ),
        (
            (r"\@ATTRIBUTE", "partial"),
            0,
            r'Warning: Name "\@ATTRIBUTE" (all uppercase) has nameType="partial". Since the name only has uppercase letters, there is no difference to nameType="any".',
        ),
        (
            ("LINK(link)", "partial"),
            0,
            'Warning: Name "LINK(link)" (all uppercase) has nameType="partial". Since the name only has uppercase letters, there is no difference to nameType="any".',
        ),
        # Mixed upper and lower case names
        (
            ("objectOBJECTobjectOBJECT", ""),
            0,
            'Name "objectOBJECTobjectOBJECT" (mixed upper and lower case) has no nameType, assuming "specified".',
        ),
        (
            ("fieldFIELDfieldFIELD", ""),
            0,
            'Name "fieldFIELDfieldFIELD" (mixed upper and lower case) has no nameType, assuming "specified".',
        ),
        (
            (r"\@attributeATTRIBUTEattributeATTRIBUTE", ""),
            0,
            r'Name "\@attributeATTRIBUTEattributeATTRIBUTE" (mixed upper and lower case) has no nameType, assuming "specified".',
        ),
        (
            ("linkLINKlinkLINK(link)", ""),
            0,
            'Name "linkLINKlinkLINK(link)" (mixed upper and lower case) has no nameType, assuming "specified".',
        ),
        (
            ("objectOBJECTobjectOBJECT", "any"),
            0,
            'Warning: Name "objectOBJECTobjectOBJECT" (mixed upper and lower case) has nameType="any", which makes it fully renameable. Is that intentional?',
        ),
        (
            ("fieldFIELDfieldFIELD", "any"),
            0,
            'Warning: Name "fieldFIELDfieldFIELD" (mixed upper and lower case) has nameType="any", which makes it fully renameable. Is that intentional?',
        ),
        (
            (r"\@attributeATTRIBUTEattributeATTRIBUTE", "any"),
            0,
            r'Warning: Name "\@attributeATTRIBUTEattributeATTRIBUTE" (mixed upper and lower case) has nameType="any", which makes it fully renameable. Is that intentional?',
        ),
        (
            ("linkLINKlinkLINK", "any"),
            0,
            'Warning: Name "linkLINKlinkLINK" (mixed upper and lower case) has nameType="any", which makes it fully renameable. Is that intentional?',
        ),
    ],
)
def test_nyaml2nxdl_prohibited_nameType(
    tmp_path, input_tuple, exit_code, expected_error
):
    """Run nyaml2nxdl on individual incorrect elements and check errors."""

    def ordered_dict_representer(dumper, value):
        return dumper.represent_mapping("tag:yaml.org,2002:map", value.items())

    yaml.add_representer(OrderedDict, ordered_dict_representer)

    key, name_type_value = input_tuple

    # Create the structure with the tuple data
    yaml_data = OrderedDict(
        {
            "category": "base",
            "doc": "Test",
            "NXtest": OrderedDict({key: {"nameType": name_type_value}}),
        }
    )

    # Write the YAML to a file
    test_file = Path(tmp_path) / "test.yaml"
    with open(test_file, "w") as file:
        yaml.dump(yaml_data, file, default_flow_style=False, allow_unicode=True)

    runner = CliRunner()
    result = runner.invoke(
        nyaml2nxdl.launch_tool,
        [str(test_file), "--output-file", str(Path(tmp_path) / "out.nxdl.xml")],
    )

    # Assert that we encounter the expected error message
    assert result.exit_code == exit_code
    assert expected_error in (str(result.output) + str(result.exception))


def test_nxdl2yaml_nameType():
    """
    Check the correct handling of the nameType attribute for the direction
    nxdl->nyaml.
    """
    ref_xml_file = "tests/data/nxdl2yaml/allowed_nameType.nxdl.xml"
    ref_yml_file = "tests/data/nxdl2yaml/ref_allowed_nameType.yaml"
    test_yml_file = "tests/data/nxdl2yaml/allowed_nameType_parsed.yaml"

    result = CliRunner().invoke(nyaml2nxdl.launch_tool, [ref_xml_file])
    assert result.exit_code == 0
    check_file_fresh_baked(test_yml_file)

    result = filecmp.cmp(ref_yml_file, test_yml_file, shallow=False)

    assert result, "Ref YML and parsed YML don't have the same structure!"
    os.remove(test_yml_file)
    sys.stdout.write("Test on xml -> yml nameType okay.\n")

    result = CliRunner().invoke(nyaml2nxdl.launch_tool, [ref_xml_file])
    assert result.exit_code == 0
    check_file_fresh_baked(test_yml_file)

    result = filecmp.cmp(ref_yml_file, test_yml_file, shallow=False)
    assert result, (
        "Ref YML and parsed YML\
has not the same structure!!"
    )
    os.remove(test_yml_file)
    sys.stdout.write("Test on xml -> yml doc formatting okay.\n")


def test_nxdl2yaml_doc_format_and_nxdl_part_as_comment():
    """
    This test for two reasons:
        1. In test-1 an nxdl file with all kind of doc formats are translated
    to yaml to check if they are correct.
        2. In test-2: Check the nxdl that comes at the end of yaml file as comment.
    """
    ref_xml_file = "tests/data/Ref_NXentry.nxdl.xml"
    ref_yml_file = "tests/data/Ref_NXentry.yaml"
    test_yml_file = "tests/data/Ref_NXentry_parsed.yaml"
    result = CliRunner().invoke(nyaml2nxdl.launch_tool, [ref_xml_file])
    assert result.exit_code == 0
    check_file_fresh_baked(test_yml_file)

    result = filecmp.cmp(ref_yml_file, test_yml_file, shallow=False)
    assert result, (
        "Ref YML and parsed YML\
has not the same structure!!"
    )
    os.remove(test_yml_file)
    sys.stdout.write("Test on xml -> yml doc formatting okay.\n")


def test_fileline_error():
    """
    In this test the yaml fileline in the error message is tested.
    """
    test_yml_file = "tests/data/NXfilelineError1.yaml"
    out_nxdl = "tests/data/NXfilelineError1.nxdl.xml"
    out_yaml = "tests/data/temp_NXfilelineError1.yaml"
    result = CliRunner().invoke(nyaml2nxdl.launch_tool, [test_yml_file])
    assert result.exit_code == 1
    assert "13" in str(result.exception)
    os.remove(out_nxdl)
    os.remove(out_yaml)

    test_yml_file = "tests/data/NXfilelineError2.yaml"
    out_nxdl = "tests/data/NXfilelineError2.nxdl.xml"
    out_yaml = "tests/data/temp_NXfilelineError2.yaml"
    result = CliRunner().invoke(nyaml2nxdl.launch_tool, [test_yml_file])
    assert result.exit_code == 1
    assert "21" in str(result.exception)
    os.remove(out_nxdl)
    os.remove(out_yaml)

    test_yml_file = "tests/data/NXfilelineError3.yaml"
    out_nxdl = "tests/data/NXfilelineError3.nxdl.xml"
    out_yaml = "tests/data/temp_NXfilelineError3.yaml"
    result = CliRunner().invoke(nyaml2nxdl.launch_tool, [test_yml_file])
    assert result.exit_code == 1
    assert "25" in str(result.exception)
    os.remove(out_nxdl)
    os.remove(out_yaml)

    sys.stdout.write("Test on xml -> yml fileline error handling okay.\n")


def test_symbols():
    """
    Check the correct parsing of symbols
    """
    ref_xml_symbol_file = "tests/data/Ref_NXnested_symbols.nxdl.xml"
    test_yml_symbol_file = "tests/data/NXnested_symbols.yaml"
    test_xml_symbol_file = "tests/data/NXnested_symbols.nxdl.xml"
    desired_matches = ["<symbols>", "</symbols>", "<symbols"]
    compare_matches(
        ref_xml_symbol_file, test_yml_symbol_file, test_xml_symbol_file, desired_matches
    )
    os.remove("tests/data/NXnested_symbols.nxdl.xml")
    sys.stdout.write("Test on symbols okay.\n")


def test_symbols_and_enum_docs():
    """
        Check the correct handling of empty attributes
    or attributes fields, e.g. doc
    """
    ref_xml_file = "tests/data/Ref_NXmytests.nxdl.xml"
    test_yml_file = "tests/data/NXmytests.yaml"
    test_xml_file = "tests/data/NXmytests.nxdl.xml"
    desired_matches = [
        "<attribute",
        "</attribute>",
        "<doc>",
        "</doc>",
        "<symbols>",
        "</symbols>",
        "<symbols",
        "<dimensions",
        "</dimensions>",
        "<dim",
    ]
    compare_matches(ref_xml_file, test_yml_file, test_xml_file, desired_matches)
    os.remove("tests/data/NXmytests.nxdl.xml")
    sys.stdout.write("Test on docs in enumeration and symbols okay.\n")


def test_enumerations_nyaml2nxdl():
    """
    Check the correct handling of enumerations (closed and open ones) for the direction
    nyaml->nxdl.
    """
    ref_xml_file = "tests/data/yaml2nxdl/ref_enumerations.nxdl.xml"
    test_yml_file = "tests/data/yaml2nxdl/enumerations.yaml"
    test_xml_file = "tests/data/yaml2nxdl/enumerations.nxdl.xml"
    desired_matches = [
        "<enumeration",
        "</enumeration>",
        "<item",
        "</item>",
        "<doc>",
        "</doc>",
        "<!--",
    ]
    compare_matches(ref_xml_file, test_yml_file, test_xml_file, desired_matches)
    os.remove(test_xml_file)
    sys.stdout.write("Test on open/closed enumerations okay.\n")


def test_nxdl2yaml_enumerations():
    """
    Check the correct handling of enumerations (closed and open ones) for the direction
    nxdl->nyaml.
    """
    ref_xml_file = "tests/data/nxdl2yaml/enumerations.nxdl.xml"
    ref_yml_file = "tests/data/nxdl2yaml/ref_enumerations.yaml"
    test_yml_file = "tests/data/nxdl2yaml/enumerations_parsed.yaml"

    result = CliRunner().invoke(
        nyaml2nxdl.launch_tool, ["--do-not-store-nxdl", str(ref_xml_file)]
    )
    assert result.exit_code == 0
    check_file_fresh_baked(test_yml_file)

    result = filecmp.cmp(ref_yml_file, test_yml_file, shallow=False)

    assert result, "Ref YML and parsed YML don't have the same structure!"
    os.remove(test_yml_file)
    sys.stdout.write("Test on xml -> yml doc formatting okay.\n")


def test_xml_parsing():
    """
        In this test an xml file in converted to yml and then back to xml.
    The xml trees of the two files are then compared.
    """
    ref_xml_file = "tests/data/Ref_NXellips.nxdl.xml"
    test_yml_file = "tests/data/Ref_NXellips_parsed.yaml"
    test_xml_file = "tests/data/Ref_NXellips_parsed.nxdl.xml"
    result = CliRunner().invoke(nyaml2nxdl.launch_tool, [ref_xml_file])
    assert result.exit_code == 0
    check_file_fresh_baked(test_yml_file)
    result = CliRunner().invoke(nyaml2nxdl.launch_tool, [test_yml_file])
    assert result.exit_code == 0
    check_file_fresh_baked(test_xml_file)

    test_tree = ET.parse(test_xml_file)
    test_tree_flattened = {i.tag.split("}", 1)[1] for i in test_tree.iter()}

    ref_tree = ET.parse(ref_xml_file)
    ref_tree_flattened = {i.tag.split("}", 1)[1] for i in ref_tree.iter()}

    assert test_tree_flattened == ref_tree_flattened, (
        "Ref XML and parsed XML\
has not the same tree structure!!"
    )
    os.remove(test_xml_file)
    os.remove(test_yml_file)
    sys.stdout.write("Test on xml -> yml -> xml okay.\n")


def test_yml_parsing():
    """In this test an xml file in converted to yml and then back to xml.
    The xml trees of the two files are then compared.
    """
    ref_yml_file = "tests/data/Ref_NXellipsometry.yaml"
    test_xml_file = "tests/data/Ref_NXellipsometry.nxdl.xml"
    test_yml_file = "tests/data/Ref_NXellipsometry_parsed.yaml"
    result = CliRunner().invoke(nyaml2nxdl.launch_tool, [ref_yml_file])
    assert result.exit_code == 0
    check_file_fresh_baked(test_xml_file)
    result = CliRunner().invoke(nyaml2nxdl.launch_tool, [test_xml_file])
    assert result.exit_code == 0
    check_file_fresh_baked(test_yml_file)

    test_yml_tree = nyaml2nxdl_forward_tools.yml_reader(test_yml_file)

    ref_yml_tree = nyaml2nxdl_forward_tools.yml_reader(ref_yml_file)

    assert list(test_yml_tree) == list(ref_yml_tree), (
        "Ref YML and parsed YML \
has not the same root entries!!"
    )
    os.remove("tests/data/Ref_NXellipsometry_parsed.yaml")
    os.remove("tests/data/Ref_NXellipsometry.nxdl.xml")
    sys.stdout.write("Test on yml -> xml -> yml okay.\n")


def test_yml_consistency_comment_parsing():
    """Test comments parsing from yaml. Convert 'yaml' input file to '.nxdl.xml' and
    '.nxdl.xml' to '.yaml'
    """

    ref_yml_file = "tests/data/Ref_NXcomment.yaml"
    test_yml_file = "tests/data/Ref_NXcomment_consistency.yaml"

    result = CliRunner().invoke(
        nyaml2nxdl.launch_tool, ["--check-consistency", ref_yml_file]
    )
    assert result.exit_code == 0, (
        f"Exception: {result.exception}, \nExecution Info:{{result.exc_info}}"
    )
    with open(ref_yml_file, "r", encoding="utf-8") as ref_yml:
        loader = LineLoader(ref_yml)
        ref_loaded_yaml = loader.get_single_data()
    ref_comment_blocks = CommentCollector(ref_yml_file, ref_loaded_yaml)
    ref_comment_blocks.extract_all_comment_blocks()

    with open(test_yml_file, "r", encoding="utf-8") as test_yml:
        loader = LineLoader(test_yml)
        test_loaded_yaml = loader.get_single_data()
    test_comment_blocks = CommentCollector(test_yml_file, test_loaded_yaml)
    test_comment_blocks.extract_all_comment_blocks()

    for ref_cmnt, test_cmnt in zip(ref_comment_blocks, test_comment_blocks):
        assert ref_cmnt == test_cmnt, "Comment is not consistent."

    os.remove(test_yml_file)


def test_conversion():
    """
    Test conversion of NXentry
    """
    root = Path(__file__).parent / "data" / "NXentry.nxdl.xml"
    result = CliRunner().invoke(nyaml2nxdl.launch_tool, [str(root)])
    assert result.exit_code == 0
    # Replace suffixes
    yaml = root.parent / Path(root.with_suffix("").stem + "_parsed.yaml")
    result = CliRunner().invoke(nyaml2nxdl.launch_tool, [str(yaml)])
    assert result.exit_code == 0
    new_root = yaml.with_suffix(".nxdl.xml")
    with open(root, encoding="utf-8", mode="r") as tmp_f:
        root_content = tmp_f.readlines()
    with open(new_root, encoding="utf-8", mode="r") as tmp_f:
        new_root_content = tmp_f.readlines()
    assert root_content == new_root_content
    Path.unlink(yaml)
    Path.unlink(new_root)


def test_yaml2nxdl_doc():
    """To test the doc style from yaml to nxdl."""
    pwd = Path(__file__).parent

    doc_file = pwd / "data/doc_yaml2nxdl.yaml"
    ref_doc_file = pwd / "data/ref_doc_yaml2nxdl.nxdl.xml"
    out_doc_file = (
        pwd / "data/doc_yaml2nxdl.nxdl.xml"
    )  # doc_file.with_suffix('.nxdl.xml')
    # Test yaml2nxdl
    # Generates '../data/doc_text.nxdl.xml'
    result = CliRunner().invoke(nyaml2nxdl.launch_tool, [str(doc_file)])
    if result.exit_code != 0:
        Path.unlink(out_doc_file)
    assert result.exit_code == 0, f"Error: Having issue running input file {doc_file}."
    # Check copyright year and repalce it according to the ref file
    check_and_replace_latest_copyright(out_doc_file)
    ref_nxdl = ET.parse(str(ref_doc_file)).getroot()
    out_nxdl = ET.parse(str(out_doc_file)).getroot()

    def compare_nxdl_doc(parent1, parent2):
        if len(parent1) > 0 and len(parent2) > 0:
            for par1, par2 in zip(parent1, parent2):
                compare_nxdl_doc(par1, par2)

        elif (
            remove_namespace_from_tag(parent1.tag) == "doc"
            and remove_namespace_from_tag(parent2.tag) == "doc"
        ):
            assert parent1.text == parent2.text, (
                f"DOCS ARE NOT SAME: node {parent1}, node {parent2}"
            )

    compare_nxdl_doc(ref_nxdl, out_nxdl)

    Path.unlink(out_doc_file)


def test_nxdl2yaml_doc():
    """To test the doc style from nxdl to yaml."""

    pwd = Path(__file__).parent
    nxdl_file = pwd / "data/doc_nxdl2yaml.nxdl.xml"
    ref_yaml = pwd / "data/ref_doc_nxdl2yaml.yaml"
    parsed_yaml_file = pwd / "data/doc_nxdl2yaml_parsed.yaml"

    result = CliRunner().invoke(
        nyaml2nxdl.launch_tool, ["--do-not-store-nxdl", str(nxdl_file)]
    )

    if result.exit_code != 0:
        Path.unlink(parsed_yaml_file)

    assert result.exit_code == 0, "Error in converter execution."

    with open(ref_yaml, mode="r", encoding="utf-8") as yaml1, open(
        parsed_yaml_file, mode="r", encoding="utf-8"
    ) as yaml2:
        yaml_dict1 = LineLoader(yaml1).get_single_data()
        yaml_dict2 = LineLoader(yaml2).get_single_data()

    compare_yaml_content(yaml_dict1, yaml_dict2, ["doc"])
    Path.unlink(parsed_yaml_file)


def test_yaml2nxdl_no_tabs(tmp_path):
    """
    Test the proper conversion of yaml2nxdl without producing tabs.
    """
    pwd = Path(__file__).parent

    doc_file = pwd / "data/no_tabs_yaml2nxdl.yaml"
    ref_doc_file = pwd / "data/ref_no_tabs_yaml2nxdl.nxdl.xml"
    out_doc_file = tmp_path / "no_tabs_yaml2nxdl.nxdl.xml"
    result = CliRunner().invoke(
        nyaml2nxdl.launch_tool, [str(doc_file), "--output-file", str(out_doc_file)]
    )
    assert result.exit_code == 0, f"Error: Having issue running input file {doc_file}."
    check_and_replace_latest_copyright(out_doc_file)
    ref_nxdl = ET.parse(str(ref_doc_file)).getroot()
    out_nxdl = ET.parse(str(out_doc_file)).getroot()

    def compare_nxdl_doc(parent1, parent2):
        if len(parent1) > 0 and len(parent2) > 0:
            for par1, par2 in zip(parent1, parent2):
                compare_nxdl_doc(par1, par2)

        elif (
            remove_namespace_from_tag(parent1.tag) == "doc"
            and remove_namespace_from_tag(parent2.tag) == "doc"
        ):
            assert parent1.text == parent2.text, (
                f"DOCS ARE NOT SAME: node {parent1}, node {parent2}"
            )

    compare_nxdl_doc(ref_nxdl, out_nxdl)


# the copyright-year needs to be a part of the yaml file as not necessarily
# every yaml file that gets a yaml2nxdl conversion is necessarily a new definition
# namely the current use case does not allow people to recover accidentally
# removed XML files when they still have their corresponding YAML file, upon conversion
# the copyright will then be changed to a copyright year as if the definition was
# just defined completely from scratch anew which is incorrect.
def test_copyright_license_new_yaml(tmp_path):
    pass
    """While converting the newly developed yaml to nxdl the license text should have
    the latest year.
    """
    """
    pwd = Path(__file__).parent
    input_file = pwd / "data/dim_keyword.yaml"
    output = tmp_path / "dim_keyword.nxdl.xml"

    result = CliRunner().invoke(
        nyaml2nxdl.launch_tool, [str(input_file), "--output-file", str(output)]
    )
    assert result.exit_code == 0, (
        f"Error in converter execution input file {input_file}."
    )
    # Check if the latest copyright year is written
    check_and_replace_latest_copyright(output)
    """


def test_check_copyright_license_in_full_modification_yaml_cycle(tmp_path):
    pwd = Path(__file__).parent
    nxdl_file = pwd / "data/Ref_NXentry_Licence.nxdl.xml"
    yaml_file = tmp_path / "Ref_NXentry_Licence_parsed.yaml"
    modified_yaml_gen = tmp_path / "Ref_NXentry_Licence_modified.yaml"
    modified_yaml_ref = pwd / "data/Ref_NXentry_Licence_modified.yaml"
    latest_nxdl = tmp_path / "Ref_NXentry_Licence_modified.nxdl.xml"

    result = CliRunner().invoke(
        nyaml2nxdl.launch_tool, [str(nxdl_file), "--output-file", str(yaml_file)]
    )
    assert result.exit_code == 0, (
        f"Error in converter execution input file {nxdl_file}."
    )
    content = yaml_file.read_text()
    find_pattern = r"my nice doc string in root level, line 2."
    replace_pattern = "my nice doc string in root level, line 2. Modified."
    updated_content = re.sub(find_pattern, replace_pattern, content)
    modified_yaml_gen.write_text(updated_content)
    # Compare two yaml and modified yaml
    with open(modified_yaml_gen, mode="r", encoding="utf-8") as gen_yaml, open(
        modified_yaml_ref, mode="r", encoding="utf-8"
    ) as ref_yaml:
        gen_yaml_dict = LineLoader(gen_yaml).get_single_data()
        ref_yaml_dict = LineLoader(ref_yaml).get_single_data()
    compare_yaml_content(gen_yaml_dict, ref_yaml_dict, ["doc"])

    # Convert modified yaml to nxdl
    result = CliRunner().invoke(
        nyaml2nxdl.launch_tool,
        [str(modified_yaml_gen), "--output-file", str(latest_nxdl)],
    )

    gen_license_text = get_nxdl_copyright_license(latest_nxdl)
    original_license_text = get_nxdl_copyright_license(nxdl_file)
    assert gen_license_text == original_license_text, "License text is not correct."


def test_check_copyright_license_in_modified_yaml(tmp_path):
    """While converting the modified yaml to nxdl the license text should
    come from stored nxdl file.
    """
    pwd = Path(__file__).parent
    yaml_file = pwd / "data/Ref_NXentry.yaml"
    modified_yaml = tmp_path / "NXentry_modified.yaml"
    output = tmp_path / "NXentry_modified.nxdl.xml"

    content = yaml_file.read_text()
    find_pattern = r"my nice doc string in root level, line 2."
    replace_pattern = "my nice doc string in root level, line 2. Modified."
    updated_content = re.sub(find_pattern, replace_pattern, content)
    modified_yaml.write_text(updated_content)

    result = CliRunner().invoke(
        nyaml2nxdl.launch_tool, [str(modified_yaml), "--output-file", str(output)]
    )
    assert result.exit_code == 0, (
        f"Error in converter execution input file {modified_yaml}."
    )

    expected_text = (
        r"Copyright \(C\) 2010-2020 NeXus International Advisory Committee \(NIAC\)"
    )

    license_text = get_nxdl_copyright_license(output)
    assert license_text, "License text not found in nxdl file."
    text_list = re.findall(expected_text, license_text, re.DOTALL)

    assert len(text_list) == 1, "License text is not correct."


@pytest.mark.parametrize(
    "test_input,output,is_valid",
    [
        (
            """
    xref:
        spec: <spec>
        term: <term>
        url: <url>
    """,
            "This concept is related to term `<term>`_ "
            "of the <spec> standard.\n\n.. _<term>: <url>",
            True,
        ),
        (
            """
    xref:
        spec: <spec>
         term: <term>
        url: <url>
    """,
            "Found invalid xref. Please make sure that your xref entries are valid yaml.",
            False,
        ),
        (
            """
    xref:
        spec: <spec>
        term: <term>
        url: <url>
        term: <term2>
    """,
            "Invalid xref. It contains nested or duplicate keys.",
            False,
        ),
        (
            """
    xref:
        spec: <spec>
        term: <term>
        url: <url>
        hallo: <term2>
    """,
            "Invalid xref. Too many keys.",
            False,
        ),
        (
            """
    xref:
        spec: <spec>
        my_key: <term>
        url: <url>
    """,
            "Invalid xref key `my_key`. Must be one of `term`, `spec` or `url`.",
            False,
        ),
        (
            """
    xref:
        spec: <spec>
        term:
            test: <nested_value>
        url: <url>
    """,
            "Invalid xref. It contains nested or duplicate keys.",
            False,
        ),
    ],
)
def test_handle_xref(test_input, output, is_valid):
    """
    Tests whether the xref generates a correct docstring.
    """
    if is_valid:
        assert handle_each_part_doc(test_input) == output
        return

    with pytest.raises(ValueError) as err:
        handle_each_part_doc(test_input)

    assert output == err.value.args[0]


@pytest.mark.parametrize(
    "test_input",
    [
        ("NXattributes"),
        ("NXcomment"),
        ("NXdimensionsType"),
        ("NXellipsometry-docCheck"),
        ("NXfit"),
    ],
)
def test_forward_conversion(test_input):
    """
    Tests if the conversion of specific test files from YAML to NXDL results as expected.
    Expected output files shall have the corresponding name with the prefix Ref_.
    """
    prefix = "tests/data/yaml2nxdl/"
    test_yml_input_file = prefix + test_input + ".yaml"
    test_xml_output_file = prefix + test_input + ".nxdl.xml"
    ref_xml_output_file = prefix + "Ref_" + test_input + ".nxdl.xml"
    runner = CliRunner()
    result = runner.invoke(nyaml2nxdl.launch_tool, [test_yml_input_file])
    assert result.exit_code == 0

    with open(test_xml_output_file, "r", encoding="utf-8") as logfile:
        log = logfile.readlines()
    with open(ref_xml_output_file, "r", encoding="utf-8") as reffile:
        ref = reffile.readlines()
    assert log == ref

    os.remove(test_xml_output_file)


@pytest.mark.parametrize(
    "test_input",
    [
        ("NXdimensionsType"),
    ],
)
def test_backward_conversion(test_input):
    """
    Tests if the conversion of specific test files from NXDL to YAML results as expected.
    Expected output files shall have the corresponding name with the prefix Ref_.
    """
    prefix = "tests/data/nxdl2yaml/"
    test_xml_input_file = prefix + test_input + ".nxdl.xml"
    test_yml_output_file = prefix + test_input + "_parsed.yaml"
    ref_yml_output_file = prefix + "Ref_" + test_input + ".yaml"
    runner = CliRunner()
    result = runner.invoke(nyaml2nxdl.launch_tool, [test_xml_input_file])
    assert result.exit_code == 0

    with open(test_yml_output_file, "r", encoding="utf-8") as logfile:
        log = logfile.readlines()
    with open(ref_yml_output_file, "r", encoding="utf-8") as reffile:
        ref = reffile.readlines()
    assert log == ref

    os.remove(test_yml_output_file)
