"""
Shared fixtures.

Unit tests build datasets in memory, one per writer family, so they run in
milliseconds and do not depend on the sample files. The sample files are used
separately, by the tests that assert real-file behavior.
"""

import contextlib
import glob
import os
import sys

import numpy as np
import pytest
import xarray as xr

sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

EXAMPLES = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
                        'sample_files')


# --- reading the sample files -------------------------------------------------
#
# The sample set is not versioned - it is 62 MB of NetCDF - and it changes as
# the models that produce it change, so no test may assume a given file, or a
# given variable inside it, is there. A missing sample is a missing input, not a
# regression: the case skips, naming what it wanted, and the rest of the suite
# (which builds its datasets in memory) runs untouched.


def sample_files(pattern='*.nc'):
    """Every sample file matching `pattern`, sorted; empty when there are none."""
    return sorted(glob.glob(os.path.join(EXAMPLES, pattern)))


def sample(name):
    """Path to one sample file, skipping the calling test when it is absent."""
    path = os.path.join(EXAMPLES, name)
    if not os.path.isfile(path):
        pytest.skip(f"{name} not in {EXAMPLES}; see README.md")
    return path


@contextlib.contextmanager
def open_sample(name, *variables):
    """
    Open a sample file and yield `(ds, report)` after normalization, skipping
    when the file is absent or does not hold every variable named.
    """
    from dispnc.io import open_source
    from dispnc.conventions import normalize

    src = open_source(sample(name))
    try:
        missing = [v for v in variables if v not in src.ds.variables]
        if missing:
            pytest.skip(f"{name} holds no {', '.join(missing)}: sample set has moved on")
        yield normalize(src.ds)
    finally:
        src.close()


@pytest.fixture
def pem_ds():
    """
    A PEM-written file: free-text units, `title` instead of `long_name`,
    no axis or standard_name anywhere.
    """
    lat = np.linspace(90, -90, 5)
    lon = np.linspace(-180, 157.5, 4)
    return xr.Dataset(
        {'tsurf': (('Time', 'latitude', 'longitude'),
                   np.arange(2 * 5 * 4, dtype='f8').reshape(2, 5, 4),
                   {'title': 'Surface temperature', 'units': 'K'})},
        coords={
            'Time': ('Time', [0.0, 1.0], {'title': 'Year of run', 'units': 'Planetary year'}),
            'latitude': ('latitude', lat, {'title': 'Latitudes', 'units': 'Degree North-South'}),
            'longitude': ('longitude', lon, {'title': 'Longitudes', 'units': 'Degree East-West'}),
        },
        attrs={'title': 'Diagnostic file for the PEM'},
    )


@pytest.fixture
def pem_radian_ds():
    """
    A PEM restart on the unstructured grid, with coordinates in radians declared
    as "Radian North-South".
    """
    return xr.Dataset(
        {'tsoil': (('physical_points',), np.arange(4, dtype='f8'),
                   {'title': 'Soil temperature', 'units': 'K'}),
         'latitude': ('physical_points', np.array([np.pi / 2, 0.0, 0.0, -np.pi / 2]),
                      {'units': 'Radian North-South'}),
         'longitude': ('physical_points', np.array([0.0, -np.pi, 0.0, 0.0]),
                       {'units': 'Radian East-West'})},
        attrs={'title': 'Starting file for the PEM'},
    )


@pytest.fixture
def xios_ds():
    """
    A XIOS file: properly CF, except that soildepth's standard_name is really a
    long_name, and the calendar is user_defined.
    """
    return xr.Dataset(
        {'ps_avg': (('time_counter', 'lat', 'lon'),
                    np.arange(1 * 3 * 4, dtype='f4').reshape(1, 3, 4),
                    {'long_name': 'Surface Pressure', 'units': 'Pa',
                     'online_operation': 'average'})},
        coords={
            'time_counter': ('time_counter', [0.0],
                             {'axis': 'T', 'standard_name': 'time',
                              'calendar': 'user_defined',
                              'units': 'days since 0001-01-01 00:00:00'}),
            'lat': ('lat', np.linspace(90, -90, 3),
                    {'axis': 'Y', 'standard_name': 'latitude', 'units': 'degrees_north'}),
            'lon': ('lon', np.linspace(-180, 90, 4),
                    {'axis': 'X', 'standard_name': 'longitude', 'units': 'degrees_east'}),
            'soildepth': ('soildepth', np.array([0.1, 0.5, 2.0]),
                          {'standard_name': 'Soil mid-layer depth',
                           'units': 'm', 'positive': 'down'}),
        },
        attrs={'title': 'Created by xios', 'description': 'Created by xios'},
    )


@pytest.fixture
def lmdz_ds():
    """
    An LMDZ dynamics restart: staggered rlonu/rlatv in radians whose only
    attribute is a French title, and an unparseable time epoch.
    """
    return xr.Dataset(
        {'ucov': (('Time', 'latitude', 'rlonu'), np.zeros((1, 3, 4)),
                  {'title': 'Vitesse U'}),
         'aire': (('latitude', 'longitude'), np.ones((3, 4)),
                  {'title': 'Aires de chaque maille'})},
        coords={
            'Time': ('Time', [0.0],
                     {'units': 'days since 0000-00-0 00:00:00', 'long_name': 'Time'}),
            'latitude': ('latitude', np.linspace(90, -90, 3), {'units': 'degrees_north'}),
            'longitude': ('longitude', np.linspace(-180, 90, 4), {'units': 'degrees_east'}),
            # 3.2398 rad is the real maximum of rlonu in start.nc
            'rlonu': ('rlonu', np.array([0.0, 1.1, 2.2, 3.2398]), {'title': 'Longitudes des points U'}),
            'rlatv': ('rlatv', np.array([1.5217, 0.0, -1.5217]), {'title': 'Latitudes des points V'}),
        },
        attrs={'title': 'Fichier demarrage dynamique'},
    )
