"""
Smoke tests for the plotting surface: every public plot function must accept its
documented call shapes and hand back an Axes with something drawn on it.
"""

import matplotlib
import pytest
import plotting_utils as plu


def _is_ax(obj):
    return isinstance(obj, matplotlib.axes.Axes)


# -- scatter / dist ---------------------------------------------------------

def test_scatter_plain(df, ax):
    assert _is_ax(plu.scatter(df, "x", "y", ax=ax))


def test_scatter_by_categorical(df, ax):
    assert _is_ax(plu.scatter(df, "x", "y", by="cat", ax=ax))


def test_scatter_by_continuous(df, ax):
    assert _is_ax(plu.scatter(df, "x", "y", by="cont", ax=ax))


def test_scatter_size_by_column(df, ax):
    assert _is_ax(plu.scatter(df, "x", "y", size="value", ax=ax))


def test_dist_plain(df, ax):
    assert _is_ax(plu.dist(df, "x", ax=ax))


def test_dist_by_categorical(df, ax):
    assert _is_ax(plu.dist(df, "x", by="cat", ax=ax))


# -- categorical plots ------------------------------------------------------

def test_counts_plot(df, ax):
    assert _is_ax(plu.counts_plot(df, "cat", ax=ax))


def test_bar_plain(df, ax):
    assert _is_ax(plu.bar(df, "cat", "value", color="grey", ax=ax))


def test_bar_by_group(df, ax):
    assert _is_ax(plu.bar(df, "cat", "value", by="group", ax=ax))


def test_box_plain(df, ax):
    assert _is_ax(plu.box(df, "cat", "value", color="grey", ax=ax))


def test_box_by_group(df, ax):
    assert _is_ax(plu.box(df, "cat", "value", by="group", ax=ax))


def test_strip_plain(df, ax):
    assert _is_ax(plu.strip(df, "cat", "value", color="k", ax=ax))


def test_violin_plain(df, ax):
    assert _is_ax(plu.violin(df, "cat", "value", color="grey", ax=ax))


@pytest.mark.parametrize("fn", ["box", "strip", "violin"])
def test_default_call_without_by(df, ax, fn):
    """With no `by` and no cmap, seaborn's own default colouring applies."""
    assert _is_ax(getattr(plu, fn)(df, "cat", "value", ax=ax))


@pytest.mark.parametrize("fn", ["box", "strip", "violin"])
def test_str_cmap_without_x_order_raises(df, ax, fn):
    """Contract: a named palette with `by=None` needs an explicit x_order."""
    with pytest.raises(ValueError):
        getattr(plu, fn)(df, "cat", "value", categorical_cmap="tab10",
                         x_order=None, ax=ax)


@pytest.mark.parametrize("fn", ["box", "strip", "violin"])
def test_dict_cmap_with_x_order(df, ax, fn):
    x_order = list(df["cat"].cat.categories)
    cmap = plu.create_palette(df, "cat", palette="tab10")
    assert _is_ax(getattr(plu, fn)(df, "cat", "value", categorical_cmap=cmap,
                                   x_order=x_order, ax=ax))


def test_bb_plot(df, ax):
    assert _is_ax(plu.bb_plot(df, cov1="cat", cov2="group", ax=ax))


# -- matrix / ranking plots -------------------------------------------------

def test_plot_heatmap(df_matrix, ax):
    assert _is_ax(plu.plot_heatmap(df_matrix, ax=ax))


def test_plot_heatmap_clustered(df_matrix, ax):
    assert _is_ax(plu.plot_heatmap(df_matrix, cluster_rows=True, cluster_cols=True, ax=ax))


def test_plot_heatmap_annotated(df_matrix, ax):
    assert _is_ax(plu.plot_heatmap(df_matrix, annot=True, fmt=".1f", ax=ax))


def test_dotplot(df, ax):
    order_x = list(df["cat"].cat.categories)
    order_y = list(df["group"].cat.categories)
    out = plu.dotplot(df, x="cat", y="group", order_x=order_x, order_y=order_y,
                      color="value", size="value", ax=ax)
    assert _is_ax(out)


def test_volcano_default_ylim(df_volcano, ax):
    """Defaults must work as shipped: ylim=2 is an int, not a float."""
    fig = ax.get_figure()
    assert _is_ax(plu.volcano(df_volcano, ax=ax, fig=fig))


def test_volcano_tuple_ylim(df_volcano, ax):
    fig = ax.get_figure()
    out = plu.volcano(df_volcano, ylim=(1.0, 4.0), ax=ax, fig=fig)
    assert _is_ax(out)


def test_volcano_bad_ylim_raises(df_volcano, ax):
    fig = ax.get_figure()
    with pytest.raises(ValueError):
        plu.volcano(df_volcano, ylim="nonsense", ax=ax, fig=fig)


def test_rank_plot(df, ax):
    fig = ax.get_figure()
    assert _is_ax(plu.rank_plot(df, cov="value", n_annotated=5, ax=ax, fig=fig))


def test_volcano_with_labels(df_volcano, ax):
    fig = ax.get_figure()
    labels = list(df_volcano.index[:5])
    out = plu.volcano(df_volcano, x="log2FC", y="-log10p", labels=labels, ax=ax, fig=fig)
    assert _is_ax(out)


# -- ax decoration ----------------------------------------------------------

def test_format_ax(df, ax):
    out = plu.format_ax(ax=ax, title="t", xlabel="x", ylabel="y", rotx=90,
                        reduced_spines=True)
    assert out.get_xlabel() == "x"
    assert out.get_ylabel() == "y"


def test_format_ax_sets_ticks(ax):
    out = plu.format_ax(ax=ax, xticks=["a", "b", "c"])
    assert [t.get_text() for t in out.get_xticklabels()] == ["a", "b", "c"]


def test_add_legend(df, ax):
    colors = plu.create_palette(df, "cat", palette="tab10")
    plu.add_legend(colors, label="cat", ax=ax)
    assert ax.get_legend() is not None


def test_add_cbar(df, ax):
    plu.add_cbar(df["cont"], palette="viridis", ax=ax, label="cont")


def test_set_rcParams():
    plu.set_rcParams()
    assert matplotlib.rcParams["figure.dpi"] is not None


def test_order_from_index(df_matrix):
    out = plu.order_from_index(df_matrix)
    assert out is not None
