import os
import shutil

import pytest
import xarray as xr
import zarr
from packaging.version import Version

zarr_version = Version(zarr.__version__)

if zarr_version >= Version("3.0.0"):
    from zarr.codecs import BloscCodec, BloscShuffle

    compressor_key = "compressors"
else:
    from zarr.codecs import Blosc as BloscCodec

    compressor_key = "compressor"

    class BloscShuffle:
        bitshuffle = zarr.codecs.blosc.BITSHUFFLE
        shuffle = zarr.codecs.blosc.SHUFFLE
        noshuffle = zarr.codecs.blosc.NOSHUFFLE


import xbitinfo as xb


@pytest.mark.parametrize("for_cdo", [True, False])
def test_get_compress_encoding_for_cdo(rasm, for_cdo):
    ds = rasm
    encoding = xb.get_compress_encoding_nc(ds, for_cdo=for_cdo)
    v = list(ds.data_vars)[0]
    time_axis = ds[v].get_axis_num("time")
    if for_cdo:
        assert encoding[v]["chunksizes"][time_axis] == 1
    else:
        assert encoding[v]["chunksizes"][time_axis] > 1


@pytest.mark.parametrize("dask", [True, False])
def test_to_compressed_netcdf(rasm, dask):
    """Test to_compressed_netcdf reduces size on disk."""
    ds = rasm
    if dask:
        ds = ds.chunk("auto")
    label = "file"
    # save
    ds.to_netcdf(f"./tmp_testdir/{label}.nc")
    ds.to_compressed_netcdf(f"./tmp_testdir/{label}_compressed.nc")
    # check size reduction
    ori_size = os.path.getsize(f"./tmp_testdir/{label}.nc")
    compressed_size = os.path.getsize(f"./tmp_testdir/{label}_compressed.nc")
    assert compressed_size < ori_size


def test_to_compressed_netcdf_for_cdo_no_time_dim_var(air_temperature):
    """Test to_compressed_netcdf if `for_cdo=True` and one var without `time_dim`."""
    ds = air_temperature
    ds["air_mean"] = ds["air"].isel(time=0)
    ds.to_compressed_netcdf("./tmp_testdir/test.nc", for_cdo=True)
    os.remove("./tmp_testdir/test.nc")


def get_zarr_size(fn):
    """Get size of zarr file excluding metadata"""
    # Open file
    grp = zarr.open_group(fn)
    # Collect size
    total = 0
    for var in list(grp.keys()):
        nbytes_stored = grp[var].nbytes_stored
        if isinstance(nbytes_stored, int):
            total += nbytes_stored
        else:
            total += nbytes_stored()
    return total


def test_to_compressed_zarr(rasm):
    """Test to_compressed_zarr reduces size on disk."""
    ds = rasm
    label = "file"
    # save
    encoding = {
        var: {compressor_key: None} for var in ds.data_vars
    }  # deactivate default compression
    ds.to_zarr(f"./tmp_testdir/{label}.zarr", mode="w", encoding=encoding)
    ds.to_compressed_zarr(f"./tmp_testdir/{label}_compressed.zarr", mode="w")
    # check size reduction
    ori_size = get_zarr_size(f"./tmp_testdir/{label}.zarr")
    compressed_size = get_zarr_size(f"./tmp_testdir/{label}_compressed.zarr")
    assert compressed_size < ori_size
    shutil.rmtree(f"./tmp_testdir/{label}.zarr")
    shutil.rmtree(f"./tmp_testdir/{label}_compressed.zarr")


def test_to_compressed_zarr_individual_compressors(eraint_uvz):
    """Test non-default compressors."""
    ds = eraint_uvz
    label = "file"
    # save
    encoding = {
        var: {compressor_key: None} for var in ds.data_vars
    }  # deactivate default compression
    ds.to_zarr(f"./tmp_testdir/{label}.zarr", mode="w", encoding=encoding)
    compressors = {
        "u": BloscCodec(cname="zstd", clevel=7),
        "v": BloscCodec(cname="zlib", clevel=7, shuffle=BloscShuffle.bitshuffle),
    }
    ds.to_compressed_zarr(
        f"./tmp_testdir/{label}_compressed.zarr", compressors, mode="w"
    )
    # check size reduction
    ori_size = get_zarr_size(f"./tmp_testdir/{label}.zarr")
    compressed_size = get_zarr_size(f"./tmp_testdir/{label}_compressed.zarr")
    assert compressed_size < ori_size
    shutil.rmtree(f"./tmp_testdir/{label}.zarr")
    shutil.rmtree(f"./tmp_testdir/{label}_compressed.zarr")
