from copy import deepcopy

import pytest
from astropy import units as u
from astropy.table import Table
from packaging.version import InvalidVersion

from stellarphot import PhotometryData
from stellarphot.settings import Camera, Observatory
from stellarphot.settings.constants import TEST_CAMERA_VALUES, TEST_OBSERVATORY_SETTINGS
from stellarphot.utils.version_migrator import VersionMigrator

TEST_CAMERA_VALUES = deepcopy(TEST_CAMERA_VALUES)
TEST_OBSERVATORY_SETTINGS = deepcopy(TEST_OBSERVATORY_SETTINGS)


class TestVersionMigrator:
    @pytest.mark.parametrize(
        "from_version, to_version",
        [
            (None, None),
            ("1", None),
            (None, "2"),
            ("1", "2"),
        ],
    )
    def test_init(self, from_version, to_version):
        # Test a few valid ways to call the constructor
        vm_args = {}
        result_version = {}
        if from_version is not None:
            vm_args["from_version"] = from_version
            result_version["from_version"] = from_version
        else:
            result_version["from_version"] = "1"

        if to_version is not None:
            vm_args["to_version"] = to_version
            result_version["to_version"] = to_version
        else:
            result_version["to_version"] = "2"

        vm = VersionMigrator(**vm_args)
        assert str(vm.from_version) == result_version["from_version"]
        assert str(vm.to_version) == result_version["to_version"]

    def test_init_bad_versions(self):
        # Test some invalid version arguments

        # Migrator only increases version
        with pytest.raises(ValueError, match="Can only migrate from a lower version"):
            VersionMigrator(from_version="2", to_version="1")

        # Nothing to do if the versions are the same
        with pytest.raises(ValueError, match="Can only migrate from a lower version"):
            VersionMigrator(from_version="2", to_version="2")

        # An invalid version should raise an error
        with pytest.raises(InvalidVersion, match="not a parsable version"):
            VersionMigrator(from_version="not a parsable version")

        # An unknown from or to version should raise an error
        with pytest.raises(ValueError, match="Unknown version"):
            VersionMigrator(from_version="3")

        with pytest.raises(ValueError, match="Unknown version"):
            VersionMigrator(to_version="3")

    # The parameter two_filters is passed to the fixture stellphotv1_photometry_data
    # and is used to determine whether the test data contains data with one or
    # two filters.
    @pytest.mark.parametrize("two_filters", [True, False])
    @pytest.mark.parametrize("exposure_unit", [None, u.second])
    def test_migrate_v1_v2_mag_names_with_band(
        self, stellphotv1_photometry_data, exposure_unit
    ):
        # Test migrating from v1 to v2
        camera = Camera(**TEST_CAMERA_VALUES)
        observatory = Observatory(**TEST_OBSERVATORY_SETTINGS)
        vm = VersionMigrator(
            from_version="1", to_version="2", camera=camera, observatory=observatory
        )
        if exposure_unit is not None:
            stellphotv1_photometry_data["exposure"] *= exposure_unit
        sp2 = vm.migrate(stellphotv1_photometry_data)

        # Make sure the instrumental magnitudes have ended up in the right place
        for passband in set(sp2["passband"]):
            in_band_2 = sp2["passband"] == passband
            in_band_1 = stellphotv1_photometry_data["filter"] == passband
            assert all(
                sp2["mag_inst"][in_band_2]
                == stellphotv1_photometry_data[f"mag_inst_{passband}"][in_band_1]
            )

        # Make sure we can initialize a PhotometryData object from the migrated data
        # To ensure we go through the full init, change sp2 to a plain Table.
        input_data = Table(sp2)
        # Remove the autogenerated column
        del input_data["night"]

        # Make the new PhotometryData object
        sp2_pd = PhotometryData(input_data=input_data)
        # Check that the data is the same
        assert all(sp2_pd == sp2)
        # Check that we really got a new object
        assert sp2_pd is not sp2
        # Check the metadata is the same
        assert sp2_pd.meta == sp2.meta
