"""Test DAP4 parsing functions."""

import os
import re
import textwrap
import unittest
from pathlib import Path
from xml.etree import ElementTree as ET

import numpy as np
import pytest
import requests

from pydap.client import open_dmr_file
from pydap.lib import walk
from pydap.model import BaseType
from pydap.parsers.dmr import DMRParser, DMRPPParser, dmr_to_dataset, get_groups


def load_dmr_file(file_path):
    abs_path = os.path.join(os.path.dirname(__file__), file_path)
    return open_dmr_file(abs_path)


def read_dmr_file(file_path):
    abs_path = os.path.join(os.path.dirname(__file__), file_path)
    with open(abs_path, "r") as dmr_file:
        dmr = dmr_file.read()
    _dmr = re.sub(' xmlns="[^"]+"', "", dmr, count=1)
    return ET.fromstring(_dmr)


DMRPPTest_file = os.path.join(
    os.path.dirname(__file__), "data/dmrs/TestGroupData.nc4.dmrpp"
)

DMRPPTest_file2 = os.path.join(os.path.dirname(__file__), "data/dmrs/MOD13Q1.hdf.dmrpp")

DMRPPTest_file3 = os.path.join(
    os.path.dirname(__file__), "data/dmrs/fill_value_scalar_no_chunks.nc4.dmrpp"
)
DMRPPTest_file4 = os.path.join(os.path.dirname(__file__), "data/dmrs/air.nc.dmrpp")
DMRPPTest_file5 = os.path.join(
    os.path.dirname(__file__), "data/dmrs/air_groups.nc.dmrpp"
)


class TestDMRParser(unittest.TestCase):
    """Test parsing a DMR."""

    def test_single_scalar(self):
        """Test a single scalar case."""
        single_scalar_dmr = (
            """<Dataset name="foo" dmrVersion="1.0"><Int32 name="x"/></Dataset>"""
        )
        dataset = dmr_to_dataset(single_scalar_dmr)
        self.assertEqual(dataset["x"].dtype, "int32")

    def test_missing_value(self):
        """Test cases with missing value."""
        NAN_dmr = """<Dataset name="foo" dmrVersion="1.0">\n    <Int32 name="x">\n
                <Attribute name="missing_value" type="Float32">\n
                            <Value>NaN</Value>\n        </Attribute>\n
                                </Int32>\n</Dataset>"""
        INF_dmr = """<Dataset name="foo" dmrVersion="1.0">\n    <Int32 name="x">\n
                <Attribute name="missing_value" type="Float32">\n
                            <Value>Inf</Value>\n        </Attribute>\n
                                </Int32>\n</Dataset>"""

        dataset_nan = dmr_to_dataset(NAN_dmr)
        dataset_inf = dmr_to_dataset(INF_dmr)

        X_nan = dataset_nan["x"]
        X_inf = dataset_inf["x"]
        self.assertEqual(np.isnan(X_nan.attributes["missing_value"]), True)
        self.assertEqual(np.isinf(X_inf.attributes["missing_value"]), True)

    def test_attribute_warning(self):
        byte_dmr = """<Dataset name="foo" dmrVersion="1.0">\n    <Int32 name="x">\n
                <Attribute name="missing_value" type="Byte">\n
                            <Value>x00</Value>\n        </Attribute>\n
                                </Int32>\n</Dataset>"""
        with self.assertRaises(Warning):
            dmr_to_dataset(byte_dmr)

    def test_coads_climatology2(self):
        dataset = load_dmr_file("data/dmrs/coads_climatology.nc.dmr")
        self.assertEqual(
            dataset["SST"].attributes["long_name"], "SEA SURFACE TEMPERATURE"
        )
        self.assertEqual(dataset["SST"].attributes["missing_value"], -9.99999979e33)
        self.assertEqual(dataset["AIRT"].shape, (12, 90, 180))
        self.assertEqual(dataset["VWND"].dtype.str, "<f4")

    def test_atl03(self):
        # Contains groups
        dataset = load_dmr_file(
            "data/dmrs/ATL03_20181228015957_13810110_003_01.2var.h5.dmrpp.dmr"
        )
        self.assertEqual(
            dataset["/gt1r/bckgrd_atlas/bckgrd_int_height"].attributes["contentType"],
            "modelResult",
        )

    def test_jpl(self):
        # Contains groups
        dataset = load_dmr_file(
            "data/dmrs/20220102090000-JPL-L4_GHRSST-SSTfnd-MUR-GLOB-v02.0-fv04.1.dmr"
        )
        self.assertEqual(dataset["sea_ice_fraction"].dims, ["/time", "/lat", "/lon"])
        self.assertEqual(dataset.attributes["Conventions"], "CF-1.7")

    def test_mod05(self):
        dataset = load_dmr_file(
            "data/dmrs/MOD05_L2.A2019336.2315.061.2019337071952.hdf.dmr"
        )
        self.assertEqual(
            dataset["Water_Vapor_Infrared"].dims,
            [
                "/Cell_Along_Swath_5km",
                "/Cell_Across_Swath_5km",
            ],
        )

    def test_SWOT(self):
        dataset = load_dmr_file("data/dmrs/SWOT_GPR.dmr")
        # pick a single variable Maps
        maps = ("/data_01/longitude", "/data_01/latitude")
        self.assertEqual(dataset["data_01/ku/swh_ocean"].Maps, maps)

    def tests_global_dimensions(self):
        dataset = load_dmr_file("data/dmrs/SimpleGroup.dmr")
        # pick a single variable Maps
        names = [key for key in dataset.dimensions.keys()]
        sizes = [v[1] for v in dataset.dimensions.items()]
        self.assertEqual(names, ["time", "nv"])
        self.assertEqual(sizes, [1, 2])

    def tests_named_dimension(self):
        dataset = load_dmr_file("data/dmrs/SimpleGroup.dmr")
        # get only names of dimensions
        names = [key for key in dataset.dimensions.keys()]
        # get all variables/arrays
        variables = []
        for var in walk(dataset, BaseType):
            variables.append(var.name)
        # assert nv is a Global dimension
        self.assertIn("nv", names)
        # assert nv is NOT a variable/array
        self.assertNotIn("nv", variables)

    def tests_FlatGroups(self):
        dataset = load_dmr_file("data/dmrs/SimpleGroupFlat.dmr")
        # pick a single variable Maps
        Groups = dataset.groups()
        Variables = [item for (item, _) in dataset.variables().items()]
        self.assertEqual(Groups, {})
        self.assertEqual(
            Variables,
            [
                "SimpleGroup/Temperature",
                "SimpleGroup/Salinity",
                "SimpleGroup/Y",
                "SimpleGroup/X",
                "time",
                "time_bnds",
            ],
        )

    def test_get_groups(self):
        dmr_file = "data/dmrs/SimpleGroup.dmr"
        node = read_dmr_file(dmr_file)
        groups = get_groups(node)
        assert isinstance(groups, dict)
        assert list(groups.keys()) == ["/SimpleGroup"]
        entry = groups["/SimpleGroup"]
        assert entry["attributes"] == {"Description": "Test group with numerical data"}
        assert entry["dimensions"] == {"Y": 4, "X": 4}
        assert entry["path"] == "/"
        assert entry["Maps"] == ()

    def test_nested_groups(self):
        dmr_file = "data/dmrs/Nested_Group.dmr"
        node = read_dmr_file(dmr_file)
        groups = get_groups(node)
        assert list(groups.keys()) == ["/Group1", "/Group1/subgroup1"]
        assert groups["/Group1"]["dimensions"] == {"lat": 1, "lon": 2}
        assert groups["/Group1/subgroup1"]["dimensions"] == {"lat": 2, "lon": 2}


class TestAttrsTypesDMRParser(unittest.TestCase):
    """Test parsing a DMR with all types"""

    def test_int8(self):
        dmr = """<Dataset name="foo" dmrVersion="1.0">\n    <Int32 name="x">\n
                <Attribute name="attr" type="Int8">\n
                            <Value>1</Value>\n        </Attribute>\n
                                </Int32>\n</Dataset>"""
        ds = dmr_to_dataset(dmr)
        assert isinstance(ds["x"].attributes["attr"], int)

    def test_uint8(self):
        dmr = """<Dataset name="foo" dmrVersion="1.0">\n    <Int32 name="x">\n
                <Attribute name="attr" type="UInt8">\n
                            <Value>1</Value>\n        </Attribute>\n
                                </Int32>\n</Dataset>"""
        ds = dmr_to_dataset(dmr)
        assert isinstance(ds["x"].attributes["attr"], int)

    def test_Char(self):
        dmr = """<Dataset name="foo" dmrVersion="1.0">\n    <Int32 name="x">\n
                <Attribute name="attr" type="Char">\n
                            <Value>1</Value>\n        </Attribute>\n
                                </Int32>\n</Dataset>"""
        ds = dmr_to_dataset(dmr)
        assert isinstance(ds["x"].attributes["attr"], int)

    def test_int16(self):
        dmr = """<Dataset name="foo" dmrVersion="1.0">\n    <Int32 name="x">\n
                <Attribute name="attr" type="Int16">\n
                            <Value>1</Value>\n        </Attribute>\n
                                </Int32>\n</Dataset>"""
        ds = dmr_to_dataset(dmr)
        assert isinstance(ds["x"].attributes["attr"], int)

    def test_uint16(self):
        dmr = """<Dataset name="foo" dmrVersion="1.0">\n    <Int32 name="x">\n
                <Attribute name="attr" type="UInt16">\n
                            <Value>1</Value>\n        </Attribute>\n
                                </Int32>\n</Dataset>"""
        ds = dmr_to_dataset(dmr)
        assert isinstance(ds["x"].attributes["attr"], int)

    def test_int32(self):
        dmr = """<Dataset name="foo" dmrVersion="1.0">\n    <Int32 name="x">\n
                <Attribute name="attr" type="Int32">\n
                            <Value>1</Value>\n        </Attribute>\n
                                </Int32>\n</Dataset>"""
        ds = dmr_to_dataset(dmr)
        assert isinstance(ds["x"].attributes["attr"], int)

    def test_uint32(self):
        dmr = """<Dataset name="foo" dmrVersion="1.0">\n    <Int32 name="x">\n
                <Attribute name="attr" type="UInt32">\n
                            <Value>1</Value>\n        </Attribute>\n
                                </Int32>\n</Dataset>"""
        ds = dmr_to_dataset(dmr)
        assert isinstance(ds["x"].attributes["attr"], int)

    def test_int64(self):
        dmr = """<Dataset name="foo" dmrVersion="1.0">\n    <Int32 name="x">\n
                <Attribute name="attr" type="Int64">\n
                            <Value>1</Value>\n        </Attribute>\n
                                </Int32>\n</Dataset>"""
        ds = dmr_to_dataset(dmr)
        assert isinstance(ds["x"].attributes["attr"], int)

    def test_uint64(self):
        dmr = """<Dataset name="foo" dmrVersion="1.0">\n    <Int32 name="x">\n
                <Attribute name="attr" type="UInt64">\n
                            <Value>1</Value>\n        </Attribute>\n
                                </Int32>\n</Dataset>"""
        ds = dmr_to_dataset(dmr)
        assert isinstance(ds["x"].attributes["attr"], int)

    def test_float32(self):
        dmr = """<Dataset name="foo" dmrVersion="1.0">\n    <Int32 name="x">\n
                <Attribute name="attr" type="Float32">\n
                            <Value>1</Value>\n        </Attribute>\n
                                </Int32>\n</Dataset>"""
        ds = dmr_to_dataset(dmr)
        assert isinstance(ds["x"].attributes["attr"], float)

    def test_float64(self):
        dmr = """<Dataset name="foo" dmrVersion="1.0">\n    <Int32 name="x">\n
                <Attribute name="attr" type="Float64">\n
                            <Value>1</Value>\n        </Attribute>\n
                                </Int32>\n</Dataset>"""
        ds = dmr_to_dataset(dmr)
        assert isinstance(ds["x"].attributes["attr"], float)

    def test_floatNone(self):
        dmr = """<Dataset name="foo" dmrVersion="1.0">\n    <Int32 name="x">\n
                <Attribute name="attr" type="Float32">\n
                            <Value></Value>\n        </Attribute>\n
                                </Int32>\n</Dataset>"""
        ds = dmr_to_dataset(dmr)
        assert ds["x"].attributes["attr"] is None

    def test_TDSfloat64(self):
        dmr = """<Dataset name="foo" dmrVersion="1.0">\n    <Int32 name="x">\n
                <Attribute name="attr" type="Float64">\n
                            <Value value="0.0"/>\n        </Attribute>\n
                                </Int32>\n</Dataset>"""
        ds = dmr_to_dataset(dmr)
        assert isinstance(ds["x"].attributes["attr"], float)

    def test_multiple_entries(self):
        dmr = """<Dataset name="foo" dmrVersion="1.0">\n    <Int32 name="x">\n
        <Attribute name="attr" type="Float32">\n
                    <Value>-1</Value>\n        <Value value="1"/>\n</Attribute>\n
                        </Int32>\n</Dataset>"""

        dmr2 = """<Dataset name="foo" dmrVersion="1.0">\n    <Int32 name="x">\n
        <Attribute name="attr" type="Float32">\n
                    <Value value="-1"/>\n        <Value>1</Value>\n</Attribute>\n
                        </Int32>\n</Dataset>"""

        ds = dmr_to_dataset(dmr)
        ds2 = dmr_to_dataset(dmr2)
        assert ds["x"].attributes["attr"] == [-1.0, 1.0]
        assert ds2["x"].attributes["attr"] == [-1.0, 1.0]

    def test_multiple_entries2(self):
        dmr = """<Dataset name="foo" dmrVersion="1.0">\n    <Int32 name="x">\n
        <Attribute name="attr" type="Float32" value="100">\n
                    <Value></Value>\n        <Value>2</Value>\n
                            <Value value="1"/>\n</Attribute>\n
                        </Int32>\n</Dataset>"""
        ds = dmr_to_dataset(dmr)
        assert ds["x"].attributes["attr"] == [100, None, 2.0, 1.0]

    def test_string(self):
        dmr = """<Dataset name="foo" dmrVersion="1.0">\n    <String name="bears">\n
                <Attribute name="attr" type="Float32">\n
                            <Value></Value>\n        </Attribute>\n
                                </String>\n</Dataset>"""
        ds = dmr_to_dataset(dmr)
        assert "bears" in ds.variables()
        assert ds["bears"].dtype == "S128"

    def test_attr_InlineValue(self):
        dmr = """
        <Dataset name="foo" dmrVersion="1.0">\n
        <String name="bears">\n
            <Attribute name="attr" type="Float32" value="100"/>\n
        </String>\n
        </Dataset>"""
        ds = dmr_to_dataset(dmr)
        assert ds["bears"].attributes["attr"] == 100.0

    def test_escapedcharacters(self):
        dmr = """
        <Dataset name="FamilyNames.h5" dmrVersion="1.0">\n
            <Group name="Last Names">\n
                <String name="Simpson"/>\n
            </Group>\n
            <Group name="First Names">\n
                <String name="Homer J."/>\n
                <String name="Bart"/>\n
                <String name="Lisa"/>\n
                <String name="Maggie"/>\n
            </Group>\n
            <Group name="Ages">\n
                <String name="35.0"/>\n
                <String name="8.5"/>\n
                <String name="7.1"/>\n
                <String name="2.1"/>\n
            </Group>\n
            <Attribute name='description' type='String' value='hello'/>\n
        </Dataset>
        """
        ds = dmr_to_dataset(dmr)
        assert ds["Last Names"].name == "Last%20Names"


@pytest.mark.parametrize(
    "ns, expected",
    [
        ("dap", "http://xml.opendap.org/ns/DAP/4.0#"),
        ("dmrpp", "http://xml.opendap.org/dap/dmrpp/1.0.0#"),
        ("xmlns", "http://www.w3.org/XML/1998/namespace"),
    ],
)
def dmrpp_parse(TestGroupDMRPP, ns, expected):
    dmrpp_instance = DMRPPParser(root=TestGroupDMRPP.read())
    assert dmrpp_instance._NS[ns] == expected


@pytest.mark.parametrize(
    "filepath, expected",
    [
        (None, "TestGroupData.nc4.h5"),
        (
            os.path.join(os.path.dirname(__file__), "FakeFileName.nc4"),
            os.path.join(os.path.dirname(__file__), "FakeFileName.nc4"),
        ),
    ],
)
def test_datafile_path(filepath, expected):
    """Test that when filepath not given, extract the filepath from the DMRpp. But if
    filepath is given, user provided filepath takes precedence.
    """
    dmrpp_instance = DMRPPParser(
        root=ET.fromstring(open(DMRPPTest_file).read()), data_filepath=filepath
    )
    assert dmrpp_instance.data_filepath == expected


@pytest.mark.parametrize(
    "Group, parsed_dims",
    [
        ("/", [{"time": 1}]),
        ("SimpleGroup", [{"Y": 40}, {"X": 40}]),
        ("data", [{"lat": 25}, {"lon": 53}]),
    ],
)
def test_parsed_dim_tag(Group, parsed_dims):
    """Check that dmrpp parses correctly the dimensions defined within
    each group (including the root group).
    """
    dmrpp_instance = DMRPPParser(root=ET.fromstring(open(DMRPPTest_file).read()))
    dim_tags = dmrpp_instance._find_dimension_tags(dmrpp_instance.find_node_fqn(Group))
    dims = [dmrpp_instance._parse_dim(dim_tag) for dim_tag in dim_tags]
    assert dims == parsed_dims


@pytest.mark.parametrize(
    "file, var_path, expected",
    [
        (
            DMRPPTest_file,
            "/SimpleGroup/Temperature",
            {
                "0.0.0": {
                    "path": "TestGroupData.nc4.h5",
                    "offset": 12762,
                    "length": 6400,
                }
            },
        ),
        (
            DMRPPTest_file,
            "/SimpleGroup/Salinity",
            {
                "0.0.0": {
                    "path": "TestGroupData.nc4.h5",
                    "offset": 19162,
                    "length": 6400,
                }
            },
        ),
        (
            DMRPPTest_file2,
            "/MODIS_Grid_16DAY_250m_500m_VI/XDim",
            {
                "0": {
                    "path": "/sidecar/MOD13Q1/1.hdf_mvs.h5",
                    "offset": 138734334,
                    "length": 6748,
                },
                "1": {
                    "path": "/sidecar/MOD13Q1/2.hdf_mvs.h5",
                    "offset": 138741082,
                    "length": 6709,
                },
                "2": {
                    "path": "/sidecar/MOD13Q1/3.hdf_mvs.h5",
                    "offset": 138747791,
                    "length": 6695,
                },
                "3": {
                    "path": "/sidecar/MOD13Q1/4.hdf_mvs.h5",
                    "offset": 138754486,
                    "length": 6495,
                },
                "4": {
                    "path": "/sidecar/MOD13Q1/5.hdf_mvs.h5",
                    "offset": 138760981,
                    "length": 4224,
                },
            },
        ),
        (
            DMRPPTest_file,
            "/data/air",
            {
                "0.0.0": {
                    "path": "TestGroupData.nc4.h5",
                    "offset": 30129,
                    "length": 2650,
                }
            },
        ),
        (
            DMRPPTest_file2,
            "/MODIS_Grid_16DAY_250m_500m_VI/eos_cf_projection",
            {
                "0": {
                    "path": "/sidecar/MOD13Q1/1.hdf_mvs.h5",
                    "offset": 8971,
                    "length": 1,
                }
            },
        ),
        (
            DMRPPTest_file3,
            "/data",
            {"entries": {}, "shape": ()},
        ),
    ],
)
def test_dmrpp_chunkmanifest_variable(file, var_path, expected):
    """Test that the chunk manifest matches the expected value. All variables have
    data that were created with single chunks.
    """
    dmrpp_instance = DMRPPParser(root=ET.fromstring(open(file).read()))
    var_tag = dmrpp_instance.find_node_fqn(var_path)
    chunk_manifest = dmrpp_instance._parse_variable(var_tag)["chunkmanifest"]

    assert chunk_manifest == expected


def test_dmrpp_parser_variable_keys():
    """Test elements of parsed variable dictionary"""
    dmrpp_instance = DMRPPParser(root=ET.fromstring(open(DMRPPTest_file).read()))
    var_tag = dmrpp_instance.find_node_fqn("SimpleGroup/Temperature")
    parsed_variable_elements = list(dmrpp_instance._parse_variable(var_tag).keys())
    expected_elements = [
        "shape",
        "data_type",
        "chunk_shape",
        "codecs",
        "dimension_names",
        "fqn_dims",
        "Maps",
        "attributes",
        "fill_value",
        "chunkmanifest",
    ]
    assert parsed_variable_elements == expected_elements


@pytest.mark.parametrize(
    "var_path, expected",
    [
        ("/data/air", ["time", "lat", "lon"]),
        ("/SimpleGroup/Temperature", ["time", "Y", "X"]),
        ("/SimpleGroup/Salinity", ["time", "Y", "X"]),
    ],
)
def test_dmrpp_dimension_names_variable(var_path, expected):
    dmrpp_instance = DMRPPParser(root=ET.fromstring(open(DMRPPTest_file).read()))
    var_tag = dmrpp_instance.find_node_fqn(var_path)
    dimension_names = dmrpp_instance._parse_variable(var_tag)["dimension_names"]
    assert dimension_names == expected


@pytest.mark.parametrize(
    "var_path, expected",
    [
        ("/data/air", ["/time", "/data/lat", "/data/lon"]),
        ("/SimpleGroup/Temperature", ["/time", "/SimpleGroup/Y", "/SimpleGroup/X"]),
        ("/SimpleGroup/Salinity", ["/time", "/SimpleGroup/Y", "/SimpleGroup/X"]),
    ],
)
def test_dmrpp_fqn_dims_variable(var_path, expected):
    dmrpp_instance = DMRPPParser(root=ET.fromstring(open(DMRPPTest_file).read()))
    var_tag = dmrpp_instance.find_node_fqn(var_path)
    dimension_names = dmrpp_instance._parse_variable(var_tag)["fqn_dims"]
    assert dimension_names == expected


@pytest.mark.parametrize(
    "var_path, expected",
    [
        ("/data/air", ["/time", "/data/lat", "/data/lon"]),
        ("/SimpleGroup/Temperature", ["/time", "/SimpleGroup/Y", "/SimpleGroup/X"]),
        ("/SimpleGroup/Salinity", ["/time", "/SimpleGroup/Y", "/SimpleGroup/X"]),
    ],
)
def test_dmrpp_Maps_variable(var_path, expected):
    dmrpp_instance = DMRPPParser(root=ET.fromstring(open(DMRPPTest_file).read()))
    var_tag = dmrpp_instance.find_node_fqn(var_path)
    dimension_names = dmrpp_instance._parse_variable(var_tag)["Maps"]
    assert dimension_names == expected


@pytest.mark.parametrize(
    "var_path, expected",
    [
        ("/data/air", (1, 25, 53)),
        ("/SimpleGroup/Temperature", (1, 40, 40)),
        ("/SimpleGroup/Salinity", (1, 40, 40)),
    ],
)
def test_dmrpp_shape_parsedvariable(var_path, expected):
    dmrpp_instance = DMRPPParser(root=ET.fromstring(open(DMRPPTest_file).read()))
    var_tag = dmrpp_instance.find_node_fqn(var_path)
    assert dmrpp_instance._parse_variable(var_tag)["shape"] == expected


@pytest.mark.parametrize(
    "var_path, expected",
    [
        ("/data/air", np.dtype(np.int16)),
        ("/SimpleGroup/Temperature", np.dtype(np.float32)),
        ("/SimpleGroup/Salinity", np.dtype(np.float32)),
    ],
)
def test_dmrpp_datatype_parsedvariable(var_path, expected):
    dmrpp_instance = DMRPPParser(root=ET.fromstring(open(DMRPPTest_file).read()))
    var_tag = dmrpp_instance.find_node_fqn(var_path)
    assert dmrpp_instance._parse_variable(var_tag)["data_type"] == expected


@pytest.mark.parametrize(
    "var_path, expected",
    [
        ("/data/air", (1, 25, 53)),
        ("/SimpleGroup/Temperature", (1, 40, 40)),
        ("/SimpleGroup/Salinity", (1, 40, 40)),
    ],
)
def test_dmrpp_chunk_shape_parsedvariable(var_path, expected):
    """Data has a single chunk."""
    dmrpp_instance = DMRPPParser(root=ET.fromstring(open(DMRPPTest_file).read()))
    var_tag = dmrpp_instance.find_node_fqn(var_path)
    assert dmrpp_instance._parse_variable(var_tag)["chunk_shape"] == expected


def test_dmrpp_attributes_parsedvariable():
    """Test attributes keys and some its elements"""
    dmrpp_instance = DMRPPParser(root=ET.fromstring(open(DMRPPTest_file).read()))
    var_tag = dmrpp_instance.find_node_fqn("/data/air")
    attrs = dmrpp_instance._parse_variable(var_tag)["attributes"]

    expected_keys = [
        "long_name",
        "units",
        "precision",
        "GRIB_id",
        "GRIB_name",
        "var_desc",
        "dataset",
        "level_desc",
        "statistic",
        "parent_stat",
        "actual_range",
        "scale_factor",
    ]

    assert list(attrs.keys()) == expected_keys

    actual_range = [round(val, 6) for val in attrs["actual_range"]]
    assert actual_range == [185.160004, 322.100006]


def test_fill_value_parsedvariable():
    """Test that demonstrates how fill values may appear for a given variable

    In addition, this DMRPP has a chunk element with a fill value to be set as nan.
    Asserts that is the case.
    """
    dmrpp_instance = DMRPPParser(root=ET.fromstring(open(DMRPPTest_file).read()))
    var_tag = dmrpp_instance.find_node_fqn("/SimpleGroup/Temperature")
    parsed_var = dmrpp_instance._parse_variable(var_tag)
    assert "_FillValue" in parsed_var["attributes"]
    assert "fill_value" in parsed_var
    assert np.isnan(parsed_var["fill_value"])


@pytest.mark.parametrize(
    "Group, skip_variables, expected_parsed_variables",
    [
        ("/", None, ["time"]),
        ("/SimpleGroup", ("Temperature", "Salinity"), ["Y", "X"]),
        ("/data", ("lat", "lon"), ["air"]),
    ],
)
def test_parsed_dataset(Group, skip_variables, expected_parsed_variables):
    """testing elements of parsed dataset"""
    dmrpp_instance = DMRPPParser(
        root=ET.fromstring(open(DMRPPTest_file).read()), skip_variables=skip_variables
    )
    variables, _ = dmrpp_instance._parse_dataset(dmrpp_instance.find_node_fqn(Group))
    assert list(variables.keys()) == expected_parsed_variables


def test_dmrpp_container_attributes():
    """Test tht the dmrpp flattens container attributes correctly at root and when a
    defined within variable.
    """
    dmrpp_instance = DMRPPParser(root=ET.fromstring(open(DMRPPTest_file3).read()))
    variables, attrs = dmrpp_instance._parse_dataset(dmrpp_instance.find_node_fqn("/"))
    assert "created" in attrs
    assert attrs["created"] == "2025-08-14T23:32:01Z"
    assert "long_name" in variables["data"]["attributes"]
    assert variables["data"]["attributes"]["long_name"] == "empty scalar data"


@pytest.mark.parametrize(
    "url, expected_dmrVersion",
    [
        ("""<Dataset name="foo" dmrVersion="1.0"><Int32 name="x"/></Dataset>""", "1.0"),
        ("""<Dataset name="foo" dmrVersion="2.0"><Int32 name="x"/></Dataset>""", "2.0"),
    ],
)
def test_dmr_version(url, expected_dmrVersion):
    dmr_instance = DMRParser(url)
    assert dmr_instance.dmrVersion == expected_dmrVersion


def test_tds_dmrVersion2():
    dmr = """
    <Dataset name="FamilyNames.h5" dmrVersion="1.0">\n
        <Attribute name='_NCProperties' type='String' value='version=2,
        netcdf=4.9.2,hdf5=1.14.4'/>\n
        <Attribute name='_DAP4_Little_Endian' type='UInt8' value="1"/>\n
    </Dataset>
    """
    dmr_instance = DMRParser(dmr)
    assert dmr_instance.dmrVersion == "2.0"


def test_nested_empty_groups_dmrVersion2():
    """Test that empty nested groups are parsed correctly with dmrVersion=2."""
    nested_empty_groups_dmr = """
    <Dataset dapVersion="4.0" dmrVersion="2.0" name="all_aligned_child_nodes.nc.h5">
        <Group name="Group1">
            <Group name="subgroup1">
                <Dimension name="lat" size="1"/>
                <Dimension name="lon" size="2"/>
                <Float64 name="subgroup_1_var">
                    <Dim name="/Group1/subgroup1/lat"/>
                    <Dim name="/Group1/subgroup1/lon"/>
                </Float64>
            </Group>
        </Group>
    </Dataset>
    """
    dataset = dmr_to_dataset(nested_empty_groups_dmr)
    assert set(["/Group1", "/Group1/subgroup1"]).issubset(dataset.groups().keys())


def test_nested_group_dmrVersion2_RelativeNameCollision():
    """Test that empty nested groups with relative name collisions are parsed correctly
    with dmrVersion=2."""
    dmr = """
    <Dataset dapVersion="4.0" dmrVersion="2.0" name="all_aligned_child_nodes.nc.h5">
        <Group name="Group1">
            <Group name="Data">
                <Dimension name="lat" size="2"/>
                <Dimension name="lon" size="2"/>
                <Float64 name="subgroup_1_var">
                    <Dim name="/Group1/Data/lat"/>
                    <Dim name="/Group1/Data/lon"/>
                </Float64>
            </Group>
        </Group>
        <Group name="Group2">
            <Group name="Data">
                <Dimension name="lat" size="1"/>
                <Dimension name="lon" size="2"/>
                <Float64 name="subgroup_2_var">
                    <Dim name="/Group2/Data/lat"/>
                    <Dim name="/Group2/Data/lon"/>
                </Float64>
            </Group>
        </Group>
    </Dataset>
    """
    ds = dmr_to_dataset(dmr)
    assert ds["Group1/Data"].dimensions == {"lat": 2, "lon": 2}
    assert ds["Group2/Data"].dimensions == {"lat": 1, "lon": 2}


def test_nested_empty_group_dmrVersion2():
    """Test that empty nested groups with relative name collisions are parsed correctly
    with dmrVersion=2."""
    dmr = """
    <Dataset dapVersion="4.0" dmrVersion="2.0" name="all_aligned_child_nodes.nc.h5">
        <Group name="Group1">
            <Group name="Group2">
                <Group name="METADATA">
                    <Attribute name="description" type="String" value="Metadata Group"/>
                </Group>
            </Group>
        </Group>
    </Dataset>
    """
    ds = dmr_to_dataset(dmr)
    assert "/Group1/Group2/METADATA" in ds.groups()
    assert hasattr(ds["Group1/Group2/METADATA"], "attributes")
    assert ds["Group1/Group2/METADATA"].attributes["description"] == "Metadata Group"


def test_nested_empty_group_dmrVersion2_somevars():
    """Test that empty nested groups with relative name collisions are parsed correctly
    with dmrVersion=2."""
    dmr = """
    <Dataset dapVersion="4.0" dmrVersion="2.0" name="all_aligned_child_nodes.nc.h5">
        <Group name="Group1">
            <Group name="Group2">
                <Group name="METADATA">
                    <Attribute name="description" type="String" value="Metadata Group"/>
                </Group>
            </Group>
        </Group>
        <Group name="Group3">
            <Group name="Group4">
                <Group name="Data">
                    <Dimension name="lat" size="1"/>
                    <Dimension name="lon" size="2"/>
                    <Float64 name="subgroup_2_var">
                        <Dim name="/Group3/Group4/Data/lat"/>
                        <Dim name="/Group3/Group4/Data/lon"/>
                    </Float64>
                </Group>
            </Group>
        </Group>
    </Dataset>
    """
    ds = dmr_to_dataset(dmr)
    assert "/Group1/Group2/METADATA" in ds.groups()
    assert hasattr(ds["Group1/Group2/METADATA"], "attributes")
    assert ds["Group1/Group2/METADATA"].attributes["description"] == "Metadata Group"


def test_compact_inline():
    dmrpp_file = (
        "http://test.opendap.org/opendap/data/dmrpp/" "compact_lowlevel.h5.dmrpp.file"
    )
    session = requests.Session()
    dmrpp = session.get(dmrpp_file).content.decode()
    dmrpp_instance = DMRPPParser(root=ET.fromstring(dmrpp))
    Vars, _ = dmrpp_instance._parse_dataset(dmrpp_instance.find_node_fqn("/"))
    inline_data = Vars["my_dataset"]["inline"]
    dtype = Vars["my_dataset"]["data_type"]
    np.testing.assert_array_equal(inline_data, np.arange(10, dtype=dtype))


def test_missingdata_inline():
    """Testing support for missingdata elements that contained inline values
    in a dmrpp
    """

    dmrpp = textwrap.dedent("""
        <Dataset xmlns="http://xml.opendap.org/ns/DAP/4.0#"
                 xmlns:dmrpp="http://xml.opendap.org/dap/dmrpp/1.0.0#"
                 dapVersion="4.0"
                 dmrVersion="1.0"
                name="file:///test.hdf"
                dmrpp:href="test.hdf"
                dmrpp:version="3.21.1">

            <Dimension name="Latitude" size="91"/>

            <Group name="Data">
                <Float64 name="Latitude">
                    <Dim name="/Latitude"/>

                    <Attribute name="units" type="String">
                        <Value>degrees_north</Value>
                    </Attribute>

                    <dmrpp:missingdata>
                        eJw1yTkKwmAUhdG3BEtLCwsLiyAiIhIS5znGIbVL+Zfm0hQ9uc3h40Z892qK+
                        I3pqZkemumumW6aqdZMV91a8cIzTzzywD133HLDNVdccsGSBXPOOeOUE445Ys
                        YhB+yzxy47jNZ2bz+77LHPAYfMOOKYE04545w5C5ZccMkV19xwyx33PPDIE8+
                        8sOL1b2LUmnHTjLtmPDTjqRmNbt4fWLdK1Q==
                    </dmrpp:missingdata>

                </Float64>
            </Group>
        </Dataset>
        """)
    dmrpp_instance = DMRPPParser(root=ET.fromstring(dmrpp))
    Vars, _ = dmrpp_instance._parse_dataset(dmrpp_instance.find_node_fqn("/Data"))
    inline_data = Vars["Latitude"]["inline"]
    dtype = Vars["Latitude"]["data_type"]
    expected = np.concatenate(
        (np.arange(-90, 90, 2, dtype=type), np.array([89.5], dtype=dtype)),
        axis=0,
    )
    np.testing.assert_array_equal(inline_data, expected[::-1])


def test_dmrpp_validation_issues_accumulation():
    dmrpp_xml_str = textwrap.dedent("""\
        <?xml version="1.0" encoding="ISO-8859-1"?>
        <Dataset xmlns="http://xml.opendap.org/ns/DAP/4.0#"
            xmlns:dmrpp="http://xml.opendap.org/dap/dmrpp/1.0.0#"
            dapVersion="4.0" dmrVersion="1.0" name="validation_test.nc"
            dmrpp:href="OPeNDAP_DMRpp_DATA_ACCESS_URL" dmrpp:version="3.21.1-451">
            <Dimension name="lat" size="25"/>
            <Float32 name="data">
                <Dim name="/lat"/>
                <Attribute type="Float32">
                    <Value>1.0</Value>
                </Attribute>
                <dmrpp:chunks fillValue="nan" byteOrder="LE">
                    <dmrpp:chunk offset="100" nBytes="100"/>
                </dmrpp:chunks>
            </Float32>
        </Dataset>
        """)
    parser = DMRPPParser(
        root=ET.fromstring(dmrpp_xml_str), data_filepath="file:///validation_test.nc"
    )
    parser._parse_dataset(parser.root)
    assert len(parser._validation_issues) > 0
    assert any(
        "Missing required attribute 'name'" in issue
        for issue in parser._validation_issues
    )


def test_dmrpp_get_attrib_with_missing_optional():
    dmrpp_xml_str = textwrap.dedent("""\
        <?xml version="1.0" encoding="ISO-8859-1"?>
        <Dataset xmlns="http://xml.opendap.org/ns/DAP/4.0#"
            xmlns:dmrpp="http://xml.opendap.org/dap/dmrpp/1.0.0#"
            dapVersion="4.0" dmrVersion="1.0" name="test.nc"
            dmrpp:href="OPeNDAP_DMRpp_DATA_ACCESS_URL" dmrpp:version="3.21.1-451">
            <Dimension name="lat" size="25"/>
        </Dataset>
        """)
    parser = DMRPPParser(root=ET.fromstring(dmrpp_xml_str))
    dimension = parser.root.find("dap:Dimension", parser._NS)
    result = parser._get_attrib(dimension, "nonexistent")
    assert result is None
    assert len(parser._validation_issues) == 1


def test_dmrpp_get_attrib_with_required_missing():
    dmrpp_xml_str = textwrap.dedent("""\
        <?xml version="1.0" encoding="ISO-8859-1"?>
        <Dataset xmlns="http://xml.opendap.org/ns/DAP/4.0#"
            xmlns:dmrpp="http://xml.opendap.org/dap/dmrpp/1.0.0#"
            dapVersion="4.0" dmrVersion="1.0" name="test.nc"
            dmrpp:href="OPeNDAP_DMRpp_DATA_ACCESS_URL" dmrpp:version="3.21.1-451">
            <Dimension name="lat" size="25"/>
        </Dataset>
        """)
    parser = DMRPPParser(root=ET.fromstring(dmrpp_xml_str))
    dimension = parser.root.find("dap:Dimension", parser._NS)
    with pytest.raises(ValueError, match="Missing required attribute 'nonexistent'"):
        parser._get_attrib(dimension, "nonexistent", required=True)


def test_dmrpp_mixed_named_and_unnamed_dimensions():
    dmrpp_xml_str = textwrap.dedent("""\
        <?xml version="1.0" encoding="ISO-8859-1"?>
        <Dataset xmlns="http://xml.opendap.org/ns/DAP/4.0#"
            xmlns:dmrpp="http://xml.opendap.org/dap/dmrpp/1.0.0#"
            dapVersion="4.0" dmrVersion="1.0" name="mixed_test.nc"
            dmrpp:href="OPeNDAP_DMRpp_DATA_ACCESS_URL" dmrpp:version="3.21.1-451">
            <Dimension name="time" size="10"/>
            <Dimension name="lat" size="30"/>
            <Float32 name="data">
                <Dim name="/time"/>
                <Dim size="20"/>
                <Dim name="/lat"/>
                <Attribute name="_FillValue" type="Float32">
                    <Value>NaN</Value>
                </Attribute>
                <dmrpp:chunks fillValue="nan" byteOrder="LE">
                    <dmrpp:chunk offset="100" nBytes="24000"/>
                </dmrpp:chunks>
            </Float32>
        </Dataset>
        """)
    parser = DMRPPParser(
        root=ET.fromstring(dmrpp_xml_str), data_filepath="file:///mixed_test.nc"
    )
    var = parser._parse_variable(parser.find_node_fqn("/data"))
    assert list(var["dimension_names"]) == ["time", "phony_dim_1", "lat"]
    assert var["shape"] == (10, 20, 30)


def test_dmrpp_phony_dim_naming():
    dmrpp_xml_str = textwrap.dedent("""\
        <?xml version="1.0" encoding="ISO-8859-1"?>
        <Dataset xmlns="http://xml.opendap.org/ns/DAP/4.0#"
            xmlns:dmrpp="http://xml.opendap.org/dap/dmrpp/1.0.0#"
            dapVersion="4.0" dmrVersion="1.0" name="phony_test.nc"
            dmrpp:href="OPeNDAP_DMRpp_DATA_ACCESS_URL" dmrpp:version="3.21.1-451">
            <Float32 name="data">
                <Dim size="10"/>
                <Dim size="20"/>
                <Attribute name="_FillValue" type="Float32">
                    <Value>NaN</Value>
                </Attribute>
                <dmrpp:chunks fillValue="nan" byteOrder="LE">
                    <dmrpp:chunk offset="100" nBytes="800"/>
                </dmrpp:chunks>
            </Float32>
        </Dataset>
        """)
    parser = DMRPPParser(
        root=ET.fromstring(dmrpp_xml_str), data_filepath="file:///phony_test.nc"
    )
    var = parser._parse_variable(parser.find_node_fqn("/data"))
    assert list(var["dimension_names"]) == ["phony_dim_0", "phony_dim_1"]
    assert var["shape"] == (10, 20)


@pytest.mark.parametrize(
    "fqn_path, expected_xpath",
    [
        ("/", "."),
        ("/air", "./*[@name='air']"),
    ],
)
def test_find_node_fqn_simple(fqn_path, expected_xpath):
    parser_instance = DMRPPParser(root=ET.fromstring(open(DMRPPTest_file4).read()))
    result = parser_instance.find_node_fqn(fqn_path)
    expected = parser_instance.root.find(expected_xpath, parser_instance._NS)
    assert result == expected


def test_find_node_fqn_grouped():
    parser_instance = DMRPPParser(root=ET.fromstring(open(DMRPPTest_file5).read()))
    result = parser_instance.find_node_fqn("/test/group/air")
    expected = parser_instance.root.find(
        "./*[@name='test']/*[@name='group']/*[@name='air']", parser_instance._NS
    )
    assert result == expected


@pytest.mark.parametrize(
    "group_path",
    [
        ("/"),
        ("/test"),
        ("/test/group"),
    ],
)
def test_split_groups(group_path):
    dmrpp_instance = DMRPPParser(root=ET.fromstring(open(DMRPPTest_file5).read()))

    # get all tags in a dataset (so all tags excluding nested groups)
    def dataset_tags(x):
        return [
            d for d in x if d.tag != "{" + dmrpp_instance._NS["dap"] + "}" + "Group"
        ]

    # check that contents of the split groups dataset match contents of the original
    #  dataset
    result_tags = dataset_tags(
        dmrpp_instance._split_groups(dmrpp_instance.root)[Path(group_path)]
    )
    expected_tags = dataset_tags(dmrpp_instance.find_node_fqn(group_path))
    assert result_tags == expected_tags


def test_parse_variable():
    parser = DMRPPParser(root=ET.fromstring(open(DMRPPTest_file4).read()))

    var = parser._parse_variable(parser.find_node_fqn("/air"))
    assert var["data_type"] == "int16"
    assert var["dimension_names"] == ["time", "lat", "lon"]
    assert var["shape"] == (2920, 25, 53)
    assert var["chunk_shape"] == (2920, 25, 53)
    # _FillValue is encoded for array dtype
    assert var["attributes"]["scale_factor"] == 0.01
    assert (
        var["attributes"]["long_name"] == "4xDaily Air temperature at sigma level 995"
    )


@pytest.mark.parametrize(
    "attr_path, expected",
    [
        ("air/long_name", {"long_name": "4xDaily Air temperature at sigma level 995"}),
        ("air/scale_factor", {"scale_factor": 0.01}),
    ],
)
def test_parse_attribute(attr_path, expected):
    parser = DMRPPParser(root=ET.fromstring(open(DMRPPTest_file4).read()))

    result = parser._parse_attribute(parser.find_node_fqn(attr_path))
    assert result == expected
