"""
Each normalization fixer, asserted independently.
"""

import numpy as np
import pytest

from dispnc.conventions import detect_families, normalize


def test_detects_each_family(pem_ds, xios_ds, lmdz_ds, pem_radian_ds):
    assert 'PEM' in detect_families(pem_ds)
    assert 'PEM' in detect_families(pem_radian_ds)
    assert 'XIOS' in detect_families(xios_ds)
    assert 'LMDZ' in detect_families(lmdz_ds)


def test_title_becomes_long_name(pem_ds):
    ds, report = normalize(pem_ds)
    assert ds['tsurf'].attrs['long_name'] == 'Surface temperature'
    # the original attribute is preserved, not moved
    assert ds['tsurf'].attrs['title'] == 'Surface temperature'


def test_existing_long_name_is_not_overwritten(xios_ds):
    ds, _ = normalize(xios_ds)
    assert ds['ps_avg'].attrs['long_name'] == 'Surface Pressure'


def test_pem_free_text_units_become_cf(pem_ds):
    ds, _ = normalize(pem_ds)
    assert ds['latitude'].attrs['units'] == 'degrees_north'
    assert ds['longitude'].attrs['units'] == 'degrees_east'
    # values were already degrees, so they must not be scaled
    assert ds['latitude'].values[0] == pytest.approx(90.0)


def test_declared_radians_are_converted(pem_radian_ds):
    ds, _ = normalize(pem_radian_ds)
    assert ds['latitude'].attrs['units'] == 'degrees_north'
    assert ds['latitude'].values[0] == pytest.approx(90.0)
    assert ds['longitude'].values[1] == pytest.approx(-180.0)


def test_unitless_radians_are_converted_including_staggered_rlonu(lmdz_ds):
    """
    rlonu reaches 3.2398 rad in start.nc. A `<= pi` test, which is what the
    original code used, would leave it in radians.
    """
    ds, report = normalize(lmdz_ds)
    assert ds['rlonu'].attrs['units'] == 'degrees_east'
    assert ds['rlonu'].values[-1] == pytest.approx(np.degrees(3.2398))
    assert ds['rlatv'].attrs['units'] == 'degrees_north'
    # the guess is always reported, never silent
    assert any('rlonu' in w for w in report.warnings)


def test_degrees_are_not_mistaken_for_radians(pem_ds):
    """
    Latitudes in degrees span far beyond pi, so the magnitude heuristic must
    decline to touch them.
    """
    ds, report = normalize(pem_ds)
    assert ds['latitude'].values.max() == pytest.approx(90.0)
    assert not any('latitude' in w for w in report.warnings)


def test_bogus_standard_name_is_scrubbed(xios_ds):
    ds, _ = normalize(xios_ds)
    attrs = ds['soildepth'].attrs
    assert 'standard_name' not in attrs
    assert attrs['dispnc_standard_name_raw'] == 'Soil mid-layer depth'
    assert attrs['long_name'] == 'Soil mid-layer depth'


def test_real_standard_name_survives(xios_ds):
    ds, _ = normalize(xios_ds)
    assert ds['lat'].attrs['standard_name'] == 'latitude'


@pytest.mark.parametrize('units,expected_units,kind', [
    ('Planetary year', 'year', 'elapsed'),
    ('days since 0000-00-0 00:00:00', 'days', 'elapsed'),
])
def test_broken_time_units_are_repaired(pem_ds, units, expected_units, kind):
    pem_ds['Time'].attrs['units'] = units
    ds, _ = normalize(pem_ds)
    assert ds['Time'].attrs['units'] == expected_units
    assert ds['Time'].attrs['dispnc_time_kind'] == kind
    assert np.isfinite(ds['Time'].values).all()


def test_user_defined_calendar_is_not_decoded(xios_ds):
    ds, _ = normalize(xios_ds)
    # stays numeric; a Mars year is not 365 days so cftime must never see it
    assert ds['time_counter'].dtype.kind in 'iuf'
    assert ds['time_counter'].attrs['dispnc_time_kind'] == 'elapsed-since'


def test_positive_is_inferred(pem_ds, xios_ds):
    ds, _ = normalize(xios_ds)
    assert ds['soildepth'].attrs['positive'] == 'down'


def test_axis_attributes_are_stamped(pem_ds):
    ds, _ = normalize(pem_ds)
    assert ds['latitude'].attrs['axis'] == 'Y'
    assert ds['longitude'].attrs['axis'] == 'X'
    assert ds['Time'].attrs['axis'] == 'T'


def test_cell_area_discovery(lmdz_ds, xios_ds):
    _, lmdz_report = normalize(lmdz_ds)
    assert lmdz_report.cell_area == 'aire'
    # XIOS files carry no area variable at all
    _, xios_report = normalize(xios_ds)
    assert xios_report.cell_area is None


def test_unstructured_grid_is_tagged(pem_radian_ds):
    _, report = normalize(pem_radian_ds)
    assert report.unstructured == {'dim': 'physical_points',
                                   'lat': 'latitude', 'lon': 'longitude'}


@pytest.mark.parametrize('fixture', ['pem_ds', 'xios_ds', 'lmdz_ds', 'pem_radian_ds'])
def test_normalize_is_idempotent(request, fixture):
    """
    Running normalize twice must not convert radians twice or double-rewrite units.
    """
    ds = request.getfixturevalue(fixture)
    once, _ = normalize(ds)
    values = {n: np.array(once[n].values, dtype=object) for n in once.variables}
    attrs = {n: dict(once[n].attrs) for n in once.variables}

    twice, _ = normalize(once)
    for name in twice.variables:
        assert dict(twice[name].attrs) == attrs[name], f"{name} attrs changed on second pass"
        np.testing.assert_array_equal(np.array(twice[name].values, dtype=object),
                                      values[name],
                                      err_msg=f"{name} values changed on second pass")
