"""
The plot-inference decision table, one case per row.

This is the test that stops the ndim ladder from growing back: every supported
combination of axis roles is pinned to a plot kind and an orientation.
"""

import numpy as np
import pytest

from dispnc.coords import Axis
from dispnc.inference import PlotPlan, TooManyDimensions, infer


def ax(role, dim, positive=None, cyclic=False, size=10):
    return Axis(role=role, dim=dim, size=size, coord=dim,
                values=np.arange(size, dtype=float), positive=positive,
                is_cyclic=cyclic, source='axis-attr', confidence=100)


X = ax('X', 'lon', cyclic=True)
Y = ax('Y', 'lat')
Z_DOWN = ax('Z', 'soildepth', positive='down')
Z_UP = ax('Z', 'altitude', positive='up')
T = ax('T', 'Time')
U = ax('U', 'physical_points')
S = ax('S', 'nslope')


@pytest.mark.parametrize('axes,kind,xdim,ydim', [
    ([],            'scalar',     None,           None),
    ([T],           'timeseries', 'Time',         None),
    ([Z_DOWN],      'profile',    None,           'soildepth'),
    ([X],           'line',       'lon',          None),
    ([U],           'line',       'physical_points', None),
    ([S],           'line',       'nslope',       None),
    ([X, Y],        'geomap',     'lon',          'lat'),
    ([Y, X],        'geomap',     'lon',          'lat'),
    ([Y, Z_DOWN],   'section',    'lat',          'soildepth'),
    ([Z_DOWN, Y],   'section',    'lat',          'soildepth'),
    ([T, Z_UP],     'section',    'Time',         'altitude'),
    ([T, X],        'section',    'Time',         'lon'),
    ([T, Y],        'section',    'Time',         'lat'),
    ([U, Z_DOWN],   'section',    'physical_points', 'soildepth'),
])
def test_decision_table(axes, kind, xdim, ydim):
    plan = infer(axes)
    assert plan.kind == kind
    assert (plan.x.dim if plan.x else None) == xdim
    assert (plan.y.dim if plan.y else None) == ydim


def test_vertical_profile_puts_depth_on_y_and_inverts():
    """
    The pre-refactor code drew profiles with depth on X. Depth belongs on Y,
    increasing downward.
    """
    plan = infer([Z_DOWN])
    assert plan.kind == 'profile'
    assert plan.x is None and plan.y.dim == 'soildepth'
    assert plan.invert_y is True


def test_altitude_profile_is_not_inverted():
    assert infer([Z_UP]).invert_y is False


def test_section_inverts_only_for_positive_down():
    assert infer([Y, Z_DOWN]).invert_y is True
    assert infer([Y, Z_UP]).invert_y is False


def test_cyclic_flag_reaches_the_plan():
    assert infer([X, Y]).cyclic is True
    assert infer([ax('X', 'lon', cyclic=False), Y]).cyclic is False


def test_three_dimensions_are_refused():
    with pytest.raises(TooManyDimensions) as err:
        infer([T, Y, X])
    assert [a.dim for a in err.value.axes] == ['Time', 'lat', 'lon']


def test_x_dim_override_swaps_a_section():
    plan = infer([Y, Z_DOWN], x_dim='soildepth')
    assert plan.x.dim == 'soildepth'
    assert plan.y.dim == 'lat'


def test_x_dim_is_ignored_on_maps(capsys):
    """
    Latitude on X used to yield a transposed map with the topography silently
    dropped. It is now refused with an explanation.
    """
    plan = infer([X, Y], x_dim='lat')
    assert plan.kind == 'geomap' and plan.x.dim == 'lon'
    assert 'ignored on geographic maps' in capsys.readouterr().out


def test_unknown_x_dim_warns_and_is_ignored(capsys):
    plan = infer([Y, Z_DOWN], x_dim='nope')
    assert plan.x.dim == 'lat'
    assert "is not among" in capsys.readouterr().out


def test_plot_kind_override():
    plan = infer([X, Y], plot_kind='section')
    assert plan.kind == 'section'
    assert plan.x.dim == 'lon' and plan.y.dim == 'lat'


def test_unknown_plot_kind_warns(capsys):
    plan = infer([X, Y], plot_kind='banana')
    assert plan.kind == 'geomap'
    assert 'unknown --plot-kind' in capsys.readouterr().out


# --- the 3D globe ----------------------------------------------------------

def test_globe_keeps_all_three_axes():
    """
    The globe is the one kind that does not reduce to two dimensions: it needs
    the vertical to build an atmospheric shell.
    """
    plan = infer([Z_UP, Y, X], plot_kind='globe')

    assert plan.kind == 'globe'
    assert (plan.x.dim, plan.y.dim, plan.z.dim) == ('lon', 'lat', 'altitude')
    assert plan.cyclic


def test_globe_axes_can_arrive_in_any_order():
    plan = infer([X, Z_UP, Y], plot_kind='globe')
    assert (plan.x.dim, plan.y.dim, plan.z.dim) == ('lon', 'lat', 'altitude')


def test_three_axes_still_refused_without_asking_for_a_globe():
    """
    A shell is opt-in: three dimensions remains an error by default.
    """
    with pytest.raises(TooManyDimensions):
        infer([Z_UP, Y, X])


def test_globe_refuses_a_subsurface_vertical():
    """
    A soil column drawn as an atmosphere would be wrong rather than merely ugly.
    """
    from dispnc.inference import SubsurfaceShell

    with pytest.raises(SubsurfaceShell) as err:
        infer([Z_DOWN, Y, X], plot_kind='globe')
    assert err.value.axis.dim == 'soildepth'


def test_globe_needs_longitude_latitude_and_a_vertical():
    with pytest.raises(TooManyDimensions):
        infer([T, Y, X], plot_kind='globe')


def test_globe_over_a_plain_map_has_no_vertical():
    """
    --plot-kind globe on a 2D field is the surface globe, so z stays unset.
    """
    plan = infer([Y, X], plot_kind='globe')
    assert plan.kind == 'globe' and plan.z is None
