"""
Writing a figure out: where it goes, what happens when it cannot go there, and
what is left open afterwards.
"""

import matplotlib
matplotlib.use('Agg')

import matplotlib.pyplot as plt
import pytest

from dispnc.figure import ensure_directory, finish_figure, save_figure


@pytest.fixture
def figure():
    fig = plt.figure()
    fig.add_subplot(1, 1, 1).plot([0, 1], [0, 1])
    yield fig
    plt.close(fig)


def test_an_output_directory_that_does_not_exist_is_created(figure, tmp_path):
    """
    `-o runs/2024/tsurf.png` in a shell loop is a reasonable thing to write, and
    it used to end in a FileNotFoundError traceback.
    """
    target = tmp_path / 'runs' / '2024' / 'tsurf.png'
    assert save_figure(figure, str(target)) == 0
    assert target.is_file()


def test_a_figure_that_cannot_be_written_says_so_instead_of_raising(figure, tmp_path,
                                                                   capsys):
    """
    A file where a directory should be. One printed line and a status, like
    every other refusal in this tool.
    """
    blocker = tmp_path / 'f.txt'
    blocker.write_text('not a directory')

    assert save_figure(figure, str(blocker / 'out.png')) == 1
    assert 'cannot' in capsys.readouterr().out


def test_an_unsupported_extension_is_reported_rather_than_raised(figure, tmp_path,
                                                                 capsys):
    assert save_figure(figure, str(tmp_path / 'out.florb')) == 1
    assert "cannot write" in capsys.readouterr().out


def test_a_saved_figure_is_closed(figure, tmp_path):
    """
    A one-shot run exits anyway, but a sweep writing one file per variable draws
    hundreds and matplotlib starts complaining at twenty.
    """
    before = set(plt.get_fignums())
    save_figure(figure, str(tmp_path / 'out.png'))
    assert figure.number not in set(plt.get_fignums()) & before


def test_a_shown_figure_is_left_open(figure, monkeypatch):
    """
    The counterpart: closing on the show path would blank the window before the
    user had seen it.
    """
    monkeypatch.setattr(plt, 'show', lambda *a, **k: None)
    assert finish_figure(figure, None) == 0
    assert figure.number in plt.get_fignums()


def test_a_video_extension_becomes_a_still(figure, tmp_path):
    """
    A figure that cannot be animated still has to land somewhere when the
    output path names a movie.
    """
    assert finish_figure(figure, str(tmp_path / 'out.mp4')) == 0
    assert (tmp_path / 'out.png').is_file()


@pytest.fixture
def greyed_map():
    """
    A map drawn with the colormap `colors.log_colormap` hands back: one that
    carries a colour for the cells a log scale could not place, and the sentence
    that says so.
    """
    import numpy as np
    from dispnc.colors import log_colormap

    fig, ax = plt.subplots()
    ax.pcolormesh(np.arange(9.0).reshape(3, 3), cmap=log_colormap('viridis', 3, 9))
    yield fig
    plt.close(fig)


def test_a_shown_figure_explains_its_greyed_cells(greyed_map, monkeypatch):
    """
    Grey cells with nothing to explain them read as missing data, which is the
    misreading the colour was chosen to prevent in the first place.
    """
    monkeypatch.setattr(plt, 'show', lambda *a, **k: None)
    finish_figure(greyed_map, None)
    notes = [t.get_text() for t in greyed_map.texts]
    assert any('off the log scale' in note for note in notes)


def test_a_saved_figure_explains_them_too_and_only_once(greyed_map, tmp_path):
    """
    The polar pair reaches `save_figure` without passing through
    `finish_figure`, and `finish_figure` reaches it *through* one - so both ends
    annotate, and the mark keeps the nested call from writing a second line over
    the first.
    """
    assert finish_figure(greyed_map, str(tmp_path / 'out.png')) == 0
    notes = [t for t in greyed_map.texts if 'off the log scale' in t.get_text()]
    assert len(notes) == 1


def test_an_ordinary_figure_says_nothing(figure, monkeypatch):
    monkeypatch.setattr(plt, 'show', lambda *a, **k: None)
    finish_figure(figure, None)
    assert not figure.texts


def test_a_window_takes_the_name_of_the_figure(figure):
    """
    A screenful of interactive figures is only comparable if they can be told
    apart in a window list.
    """
    from dispnc.figure import name_window

    figure.suptitle('Surface temperature - Time = 0 year')
    names = []
    manager = figure.canvas.manager
    if manager is None:                     # a backend with no windows at all
        pytest.skip('this backend has no figure manager')
    manager.set_window_title = names.append

    name_window(figure)
    assert names == ['Surface temperature - Time = 0 year']


def test_ensure_directory_reports_what_it_could_not_create(tmp_path, capsys):
    blocker = tmp_path / 'f.txt'
    blocker.write_text('not a directory')
    assert ensure_directory(str(blocker / 'deeper' / 'out.png')) is False
    assert 'cannot create' in capsys.readouterr().out


def test_the_baseline_harness_can_still_see_a_saved_figure(tmp_path):
    """
    A trap worth pinning. `describe_figures` reads titles and labels off figures
    that are still open, and every sweep run passes -o - so the moment
    `save_figure` began closing what it had written, the harness would have
    recorded an empty `axes` list for all 194 variables and gone on reporting
    "identical" forever. The Recorder neutralises plt.close for the duration of
    a run; this is the test that says why.
    """
    import baseline_capture

    with baseline_capture.Recorder():
        fig = plt.figure()
        fig.suptitle('Surface temperature')
        fig.add_subplot(1, 1, 1).set_ylabel('K')
        save_figure(fig, str(tmp_path / 'x.png'))
        recorded = baseline_capture.describe_figures()
    plt.close('all')

    assert recorded, 'the harness recorded nothing at all'
    assert recorded[0]['title'] == 'Surface temperature'


def test_the_harness_records_a_map_title_and_its_inset_colour_bar(tmp_path):
    """
    Both live where `fig.axes` cannot see them: the title on `fig.suptitle`,
    because a cartopy GeoAxes resolves an axes title to NaN, and the colour bar
    in an inset, because a bar taking its room out of a GeoAxes collapses the
    whole figure under bbox_inches='tight'. Between them every map was recording
    one entry with nothing in it.
    """
    import baseline_capture
    import cartopy.crs as ccrs

    fig, ax = plt.subplots(subplot_kw=dict(projection=ccrs.PlateCarree()))
    try:
        fig.suptitle('Surface temperature - Time = 0 year')
        ax.inset_axes([1.03, 0.08, 0.03, 0.72]).set_ylabel('Surface temperature (K)')
        recorded = baseline_capture.describe_figures()
    finally:
        plt.close(fig)

    titles = [entry['title'] for entry in recorded]
    labels = [entry['ylabel'] for entry in recorded]
    assert 'Surface temperature - Time = 0 year' in titles
    assert 'Surface temperature (K)' in labels


def test_a_named_size_replaces_the_default_of_each_plot_kind(figure):
    """
    Each kind keeps its own default - a profile is portrait, a map landscape -
    and --figsize overrides that rather than flattening them all to one shape.
    """
    from dispnc.figure import axes_for
    from dispnc.render.context import PlotContext

    plain = PlotContext(varname='x', data=None, plan=None)
    fig, _ = axes_for(plain, (5, 7))
    try:
        assert tuple(fig.get_size_inches()) == (5, 7)
    finally:
        plt.close(fig)

    named = PlotContext(varname='x', data=None, plan=None, figsize=(12, 5))
    fig, _ = axes_for(named, (5, 7))
    try:
        assert tuple(fig.get_size_inches()) == (12, 5)
    finally:
        plt.close(fig)


def test_a_title_the_user_asked_for_is_used_exactly_as_given():
    """
    Someone writing `--title "Fig. 3"` for a paper means "Fig. 3", not
    "Fig. 3 profile". Every renderer decorating the title in its own way is
    why this is asked in one place.
    """
    from dispnc.render.context import PlotContext

    generated = PlotContext(varname='tsoil', data=None, plan=None,
                            long_name='Soil temperature', subtitle='Time = 0 year')
    assert generated.titled('profile') == 'Soil temperature - Time = 0 year profile'

    named = PlotContext(varname='tsoil', data=None, plan=None,
                        long_name='Soil temperature', subtitle='Time = 0 year',
                        title_override='Fig. 3')
    assert named.titled('profile') == 'Fig. 3'
    assert named.title == 'Fig. 3'
