# Licensed under a 3-clause BSD style license - see LICENSE.rst

import pytest

from astropy.cosmology._src.core import _COSMOLOGY_CLASSES, Cosmology
from astropy.cosmology._src.io.builtin.row import from_row, to_row
from astropy.table import Row

from .base import ToFromDirectTestBase, ToFromTestMixinBase

###############################################################################


class ToFromRowTestMixin(ToFromTestMixinBase):
    """
    Tests for a Cosmology[To/From]Format with ``format="astropy.row"``.
    This class will not be directly called by :mod:`pytest` since its name does
    not begin with ``Test``. To activate the contained tests this class must
    be inherited in a subclass. Subclasses must define a :func:`pytest.fixture`
    ``cosmo`` that returns/yields an instance of a |Cosmology|.
    See ``TestCosmologyToFromFormat`` or ``TestCosmology`` for examples.
    """

    @pytest.mark.parametrize("in_meta", [True, False])
    def test_to_row_in_meta(self, cosmo_cls, cosmo, in_meta):
        """Test where the cosmology class is placed."""
        row = cosmo.to_format("astropy.row", cosmology_in_meta=in_meta)

        # if it's in metadata, it's not a column. And vice versa.
        if in_meta:
            assert row.meta["cosmology"] == cosmo_cls.__qualname__
            assert "cosmology" not in row.colnames  # not also a column
        else:
            assert row["cosmology"] == cosmo_cls.__qualname__
            assert "cosmology" not in row.meta

    # -----------------------

    def test_from_not_row(self, cosmo, from_format):
        """Test not passing a Row to the Row parser."""
        with pytest.raises(AttributeError):
            from_format("NOT A ROW", format="astropy.row")

    def test_tofrom_row_instance(self, cosmo, to_format, from_format):
        """Test cosmology -> astropy.row -> cosmology."""
        # ------------
        # To Row

        row = to_format("astropy.row")
        assert isinstance(row, Row)
        assert row["cosmology"] == cosmo.__class__.__qualname__
        assert row["name"] == cosmo.name

        # ------------
        # From Row

        row.table["mismatching"] = "will error"

        # tests are different if the last argument is a **kwarg
        if cosmo._init_has_kwargs:
            got = from_format(row, format="astropy.row")

            assert got.__class__ is cosmo.__class__
            assert got.name == cosmo.name
            assert "mismatching" not in got.meta

            return  # don't continue testing

        # read with mismatching parameters errors
        with pytest.raises(TypeError, match="there are unused parameters"):
            from_format(row, format="astropy.row")

        # unless mismatched are moved to meta
        got = from_format(row, format="astropy.row", move_to_meta=True)
        assert got == cosmo
        assert got.meta["mismatching"] == "will error"

        # it won't error if everything matches up
        row.table.remove_column("mismatching")
        got = from_format(row, format="astropy.row")
        assert got == cosmo

        # and it will also work if the cosmology is a class
        # Note this is not the default output of ``to_format``.
        cosmology = _COSMOLOGY_CLASSES[row["cosmology"]]
        row.table.remove_column("cosmology")
        row.table["cosmology"] = cosmology
        got = from_format(row, format="astropy.row")
        assert got == cosmo

        # also it auto-identifies 'format'
        got = from_format(row)
        assert got == cosmo

    def test_tofrom_row_rename(self, cosmo, to_format, from_format):
        """Test renaming columns in row."""
        rename = {"name": "cosmo_name"}
        row = to_format("astropy.row", rename=rename)

        assert "name" not in row.colnames
        assert "cosmo_name" in row.colnames

        # Error if just reading
        with pytest.raises(TypeError, match="there are unused parameters"):
            from_format(row)

        # Roundtrip
        inv_rename = {v: k for k, v in rename.items()}
        got = from_format(row, rename=inv_rename)
        assert got == cosmo

    def test_fromformat_row_subclass_partial_info(self, cosmo: Cosmology) -> None:
        """
        Test writing from an instance and reading from that class.
        This works with missing information.

        There are no partial info options
        """

    @pytest.mark.parametrize("format", [True, False, None, "astropy.row"])
    def test_is_equivalent_to_row(self, cosmo, to_format, format):
        """Test :meth:`astropy.cosmology.Cosmology.is_equivalent`.

        This test checks that Cosmology equivalency can be extended to any
        Python object that can be converted to a Cosmology -- in this case
        a Row.
        """
        obj = to_format("astropy.row")
        assert not isinstance(obj, Cosmology)

        is_equiv = cosmo.is_equivalent(obj, format=format)
        assert is_equiv is (format is not False)


class TestToFromRow(ToFromDirectTestBase, ToFromRowTestMixin):
    """
    Directly test ``to/from_row``.
    These are not public API and are discouraged from use, in favor of
    ``Cosmology.to/from_format(..., format="astropy.row")``, but should be
    tested regardless b/c 3rd party packages might use these in their Cosmology
    I/O. Also, it's cheap to test.
    """

    def setup_class(self):
        self.functions = {"to": to_row, "from": from_row}
