import pathlib
from typing import Any
from typing import Tuple

import h5py
import numpy
from ewoksutils import uri_utils
from silx.io.dictdump import h5todict
from silx.io.h5py_utils import retry
from silx.utils.retry import RetryError

from ._assert import demo_assert


@demo_assert("HDF5 group {data_path} in {file_path}")
@retry(retry_timeout=10, retry_period=0.2)
def assert_hdf5_group_exists(file_path: pathlib.Path, data_path: str) -> None:
    if not file_path.exists():
        raise RetryError(f"{str(file_path)!r} does not exist")
    file_path = str(file_path)
    with h5py.File(file_path, mode="r") as root:
        if data_path not in root:
            raise RetryError(f"{data_path!r} not in {file_path!r}")


@demo_assert("HDF5 dataset {data_path} in {file_path} with shape {expected_shape}")
@retry(retry_timeout=10, retry_period=0.2)
def assert_hdf5_dataset_exists(
    file_path: pathlib.Path,
    data_path: str,
    expected_shape: Tuple[int, ...],
) -> None:
    if not file_path.exists():
        raise RetryError(f"{str(file_path)!r} does not exist")
    file_path = str(file_path)
    with h5py.File(file_path, mode="r") as root:
        if data_path not in root:
            raise RetryError(f"{data_path!r} not in {file_path!r}")

        dset = root[data_path]
        if dset.shape != expected_shape:
            url = f"{file_path!r}::{data_path}"
            raise RetryError(
                f"Shape of {url!r} is {dset.shape} instead of {expected_shape}"
            )


@demo_assert("HDF5 uri {uri} exists")
@retry(retry_timeout=10, retry_period=0.2)
def assert_equal_hdf5_content(uri: str, expected: Any):
    file_path = uri_utils.path_from_uri(uri)
    query = uri_utils.parse_query(uri)
    data_path = query["path"]

    if not file_path.exists():
        raise RetryError(f"{str(file_path)!r} does not exist")
    file_path = str(file_path)
    with h5py.File(file_path, mode="r") as root:
        if data_path not in root:
            raise RetryError(f"{data_path!r} not in {file_path!r}")
        actual = h5todict(root, data_path)
        __assert_equal(actual, expected)


def __assert_equal(actual: Any, expected: Any, _path: str = "") -> None:
    if isinstance(expected, dict):
        err_msg = f"Expected dict at {_path or '/'} got {type(actual).__name__}"
        assert isinstance(actual, dict), err_msg

        actual_keys = set(actual)
        expected_keys = set(expected)
        if actual_keys != expected_keys:
            missing = expected_keys - actual_keys
            extra = actual_keys - expected_keys
            raise AssertionError(
                f"Key mismatch at {_path or '/'}: " f"missing={missing}, extra={extra}"
            )

        for key in expected:
            next_path = f"{_path}/{key}" if _path else f"/{key}"
            __assert_equal(actual[key], expected[key], _path=next_path)

    elif isinstance(actual, numpy.ndarray):
        err_msg = f"Array differ at {_path!r}"
        numpy.testing.assert_array_equal(actual, expected, err_msg=err_msg)

    else:
        err_msg = (
            f"Values differ at {_path!r}: actual={actual!r}, expected={expected!r}"
        )
        assert actual == expected, err_msg
