from django.conf import settings as s
from django.test import TestCase, override_settings
from django.urls import reverse
from envipy_additional_information import Temperature, Interval

from epdb.logic import UserManager, PackageManager
from epdb.models import Compound, Scenario, ExternalDatabase


@override_settings(MODEL_DIR=s.FIXTURE_DIRS[0] / "models", CELERY_TASK_ALWAYS_EAGER=True)
class CompoundViewTest(TestCase):
    fixtures = ["test_fixtures_incl_model.jsonl.gz"]

    # A valid V2000 molfile for 4-Nitrobenzoic acid (O=C(O)C1=CC=C([N+](=O)[O-])C=C1)
    VALID_MOLFILE = """\n  Mrv2211 01012500002D\n\n 12 12  0  0  0  0            999 V2000\n    1.4289   -0.8250    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0\n    0.7145   -0.4125    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0\n    0.0000   -0.8250    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0\n    0.0000   -1.6500    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0\n    0.7145   -2.0625    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0\n    1.4289   -1.6500    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0\n    0.7145    0.4125    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0\n    0.7145    1.2375    0.0000 O   0  0  0  0  0  0  0  0  0  0  0  0\n    1.4289    0.8250    0.0000 O   0  0  0  0  0  0  0  0  0  0  0  0\n    0.7145   -2.8875    0.0000 N   0  3  0  0  0  0  0  0  0  0  0  0\n    0.0000   -3.3000    0.0000 O   0  5  0  0  0  0  0  0  0  0  0  0\n    1.4289   -3.3000    0.0000 O   0  0  0  0  0  0  0  0  0  0  0  0\n  1  2  2  0  0  0  0\n  2  3  1  0  0  0  0\n  3  4  2  0  0  0  0\n  4  5  1  0  0  0  0\n  5  6  2  0  0  0  0\n  6  1  1  0  0  0  0\n  2  7  1  0  0  0  0\n  7  8  2  0  0  0  0\n  7  9  1  0  0  0  0\n  5 10  1  0  0  0  0\n 10 11  1  0  0  0  0\n 10 12  2  0  0  0  0\nM  CHG  2  10   1  11  -1\nM  END\n"""

    @classmethod
    def setUpClass(cls):
        super(CompoundViewTest, cls).setUpClass()
        cls.user1 = UserManager.create_user(
            "user1",
            "user1@envipath.com",
            "SuperSafe",
            set_setting=False,
            add_to_group=True,
            is_active=True,
        )
        cls.user1_default_package = cls.user1.default_package
        cls.package = PackageManager.create_package(cls.user1, "Test", "Test Pack")

    def setUp(self):
        self.client.force_login(self.user1)

    def test_create_compound(self):
        response = self.client.post(
            reverse("compounds"),
            {
                "compound-name": "1,2-Dichloroethane",
                "compound-description": "Eawag BBD compound c0001",
                "compound-smiles": "C(CCl)Cl",
            },
        )

        self.assertEqual(response.status_code, 302)
        compound_url = response.url

        c = Compound.objects.get(url=compound_url)

        self.assertEqual(c.package, self.user1_default_package)
        self.assertEqual(c.name, "1,2-Dichloroethane")
        self.assertEqual(c.description, "Eawag BBD compound c0001")
        self.assertEqual(c.default_structure.smiles, "C(CCl)Cl")
        self.assertEqual(c.default_structure.canonical_smiles, "ClCCCl")
        self.assertEqual(c.structures.all().count(), 2)
        self.assertEqual(self.user1_default_package.compounds.count(), 1)

        # Adding the same rule again should return the existing one, hence not increasing the number of rules
        response = self.client.post(
            reverse("compounds"),
            {
                "compound-name": "1,2-Dichloroethane",
                "compound-description": "Eawag BBD compound c0001",
                "compound-smiles": "C(CCl)Cl",
            },
        )

        self.assertEqual(response.url, compound_url)
        self.assertEqual(response.status_code, 302)
        self.assertEqual(self.user1_default_package.compounds.count(), 1)

        # Adding the same rule in a different package should create a new rule
        response = self.client.post(
            reverse("package compound list", kwargs={"package_uuid": self.package.uuid}),
            {
                "compound-name": "1,2-Dichloroethane",
                "compound-description": "Eawag BBD compound c0001",
                "compound-smiles": "C(CCl)Cl",
            },
        )

        self.assertEqual(response.status_code, 302)
        self.assertNotEqual(compound_url, response.url)

        # adding another reaction should increase count
        response = self.client.post(
            reverse("compounds"),
            {
                "compound-name": "2-Chloroethanol",
                "compound-description": "Eawag BBD compound c0005",
                "compound-smiles": "C(CO)Cl",
            },
        )

        self.assertEqual(response.status_code, 302)
        self.assertEqual(self.user1_default_package.compounds.count(), 2)

    # Edit
    def test_edit_rule(self):
        response = self.client.post(
            reverse("compounds"),
            {
                "compound-name": "1,2-Dichloroethane",
                "compound-description": "Eawag BBD compound c0001",
                "compound-smiles": "C(CCl)Cl",
            },
        )

        self.assertEqual(response.status_code, 302)
        compound_url = response.url

        c = Compound.objects.get(url=compound_url)

        response = self.client.post(
            reverse(
                "package compound detail",
                kwargs={
                    "package_uuid": str(self.user1_default_package.uuid),
                    "compound_uuid": str(c.uuid),
                },
            ),
            {
                "compound-name": "Test Compound Adjusted",
                "compound-description": "New Description",
            },
        )

        self.assertEqual(response.status_code, 302)
        c = Compound.objects.get(url=compound_url)
        self.assertEqual(c.name, "Test Compound Adjusted")
        self.assertEqual(c.description, "New Description")

        # Rest stays untouched
        self.assertEqual(c.default_structure.smiles, "C(CCl)Cl")
        self.assertEqual(self.user1_default_package.compounds.count(), 1)

    # Scenario
    def test_set_scenario(self):
        s1 = Scenario.create(
            self.user1_default_package,
            "Test Scen",
            "Test Desc",
            "2025-10",
            "soil",
            [Temperature(interval=Interval(start=20, end=30))],
        )

        s2 = Scenario.create(
            self.user1_default_package,
            "Test Scen2",
            "Test Desc2",
            "2025-10",
            "soil",
            [Temperature(interval=Interval(start=10, end=20))],
        )

        response = self.client.post(
            reverse("compounds"),
            {
                "compound-name": "1,2-Dichloroethane",
                "compound-description": "Eawag BBD compound c0001",
                "compound-smiles": "C(CCl)Cl",
            },
        )

        self.assertEqual(response.status_code, 302)
        compound_url = response.url

        c = Compound.objects.get(url=compound_url)

        response = self.client.post(
            reverse(
                "package compound detail",
                kwargs={"package_uuid": str(c.package.uuid), "compound_uuid": str(c.uuid)},
            ),
            {"selected-scenarios": [s1.url, s2.url]},
        )

        self.assertEqual(len(c.scenarios.all()), 2)

        response = self.client.post(
            reverse(
                "package compound detail",
                kwargs={"package_uuid": str(c.package.uuid), "compound_uuid": str(c.uuid)},
            ),
            {"selected-scenarios": [s1.url]},
        )

        self.assertEqual(len(c.scenarios.all()), 1)
        self.assertEqual(c.scenarios.first().url, s1.url)

        response = self.client.post(
            reverse(
                "package compound detail",
                kwargs={"package_uuid": str(c.package.uuid), "compound_uuid": str(c.uuid)},
            ),
            {
                # We have to set an empty string to avoid that the parameter is removed
                "selected-scenarios": ""
            },
        )

        self.assertEqual(len(c.scenarios.all()), 0)

    #
    def test_copy(self):
        response = self.client.post(
            reverse("compounds"),
            {
                "compound-name": "1,2-Dichloroethane",
                "compound-description": "Eawag BBD compound c0001",
                "compound-smiles": "C(CCl)Cl",
            },
        )

        self.assertEqual(response.status_code, 302)
        compound_url = response.url
        c = Compound.objects.get(url=compound_url)

        response = self.client.post(
            reverse(
                "package detail",
                kwargs={
                    "package_uuid": str(self.package.uuid),
                },
            ),
            {"hidden": "copy", "object_to_copy": c.url},
        )

        self.assertEqual(response.status_code, 200)

        copied_object_url = response.json()["success"]
        copied_compound = Compound.objects.get(url=copied_object_url)

        self.assertEqual(copied_compound.name, c.name)
        self.assertEqual(copied_compound.description, c.description)
        self.assertEqual(copied_compound.default_structure.smiles, c.default_structure.smiles)

        # Copy to the same package should fail
        response = self.client.post(
            reverse(
                "package detail",
                kwargs={
                    "package_uuid": str(c.package.uuid),
                },
            ),
            {"hidden": "copy", "object_to_copy": c.url},
        )

        self.assertEqual(response.status_code, 400)
        self.assertEqual(
            response.json()["error"], f"Can't copy object {compound_url} to the same package!"
        )

    def test_references(self):
        ext_db, _ = ExternalDatabase.objects.get_or_create(
            name="PubChem Compound",
            defaults={
                "full_name": "PubChem Compound Database",
                "description": "Chemical database of small organic molecules",
                "base_url": "https://pubchem.ncbi.nlm.nih.gov",
                "url_pattern": "https://pubchem.ncbi.nlm.nih.gov/compound/{id}",
            },
        )

        ext_db2, _ = ExternalDatabase.objects.get_or_create(
            name="PubChem Substance",
            defaults={
                "full_name": "PubChem Substance Database",
                "description": "Database of chemical substances",
                "base_url": "https://pubchem.ncbi.nlm.nih.gov",
                "url_pattern": "https://pubchem.ncbi.nlm.nih.gov/substance/{id}",
            },
        )

        response = self.client.post(
            reverse("compounds"),
            {
                "compound-name": "1,2-Dichloroethane",
                "compound-description": "Eawag BBD compound c0001",
                "compound-smiles": "C(CCl)Cl",
            },
        )

        self.assertEqual(response.status_code, 302)
        compound_url = response.url
        c = Compound.objects.get(url=compound_url)

        response = self.client.post(
            reverse(
                "package compound detail",
                kwargs={
                    "package_uuid": str(c.package.uuid),
                    "compound_uuid": str(c.uuid),
                },
            ),
            {"selected-database": ext_db.pk, "identifier": "25154249"},
        )

        self.assertEqual(c.external_identifiers.count(), 1)
        self.assertEqual(c.external_identifiers.first().database, ext_db)
        self.assertEqual(c.external_identifiers.first().identifier_value, "25154249")
        self.assertEqual(
            c.external_identifiers.first().url, "https://pubchem.ncbi.nlm.nih.gov/compound/25154249"
        )

        response = self.client.post(
            reverse(
                "package compound detail",
                kwargs={
                    "package_uuid": str(c.package.uuid),
                    "compound_uuid": str(c.uuid),
                },
            ),
            {"selected-database": ext_db2.pk, "identifier": "25154249"},
        )

        self.assertEqual(c.external_identifiers.count(), 2)
        self.assertEqual(c.external_identifiers.last().database, ext_db2)
        self.assertEqual(c.external_identifiers.last().identifier_value, "25154249")
        self.assertEqual(
            c.external_identifiers.last().url, "https://pubchem.ncbi.nlm.nih.gov/substance/25154249"
        )

    def test_delete(self):
        response = self.client.post(
            reverse("compounds"),
            {
                "compound-name": "1,2-Dichloroethane",
                "compound-description": "Eawag BBD compound c0001",
                "compound-smiles": "C(CCl)Cl",
            },
        )

        self.assertEqual(response.status_code, 302)
        compound_url = response.url

        c = Compound.objects.get(url=compound_url)

        response = self.client.post(
            reverse(
                "package compound detail",
                kwargs={"package_uuid": str(c.package.uuid), "compound_uuid": str(c.uuid)},
            ),
            {"hidden": "delete"},
        )

        self.assertEqual(self.user1_default_package.compounds.count(), 0)

    def test_set_aliases(self):
        alias_1 = "Alias 1"
        alias_2 = "Alias 2"

        response = self.client.post(
            reverse("compounds"),
            {
                "compound-name": "1,2-Dichloroethane",
                "compound-description": "Eawag BBD compound c0001",
                "compound-smiles": "C(CCl)Cl",
            },
        )

        self.assertEqual(response.status_code, 302)
        compound_url = response.url

        c = Compound.objects.get(url=compound_url)

        response = self.client.post(
            reverse(
                "package compound detail",
                kwargs={"package_uuid": str(c.package.uuid), "compound_uuid": str(c.uuid)},
            ),
            {"aliases": [alias_1, alias_2]},
        )

        c = Compound.objects.get(url=compound_url)
        self.assertEqual(len(c.aliases), 2)

        response = self.client.post(
            reverse(
                "package compound detail",
                kwargs={"package_uuid": str(c.package.uuid), "compound_uuid": str(c.uuid)},
            ),
            {"aliases": [alias_1]},
        )

        c = Compound.objects.get(url=compound_url)
        self.assertEqual(len(c.aliases), 1)

        response = self.client.post(
            reverse(
                "package compound detail",
                kwargs={"package_uuid": str(c.package.uuid), "compound_uuid": str(c.uuid)},
            ),
            {
                # We have to set an empty string to avoid that the parameter is removed
                "aliases": ""
            },
        )

        c = Compound.objects.get(url=compound_url)
        self.assertEqual(len(c.aliases), 0)

    def test_create_compound_via_molfile(self):
        """POSTing a valid molfile without a SMILES should create a compound successfully."""
        response = self.client.post(
            reverse("compounds"),
            {
                "compound-name": "4-Nitrobenzoic acid",
                "compound-description": "Created from molfile",
                "compound-molfile": self.VALID_MOLFILE,
            },
        )

        self.assertEqual(response.status_code, 302)
        compound_url = response.url

        c = Compound.objects.get(url=compound_url)

        self.assertEqual(c.name, "4-Nitrobenzoic acid")
        self.assertEqual(c.description, "Created from molfile")
        # SMILES should have been extracted from molfile, so it must be non-empty
        self.assertIsNotNone(c.default_structure.smiles)
        self.assertNotEqual(c.default_structure.smiles.strip(), "")

    def test_create_compound_molfile_takes_precedence_over_smiles(self):
        """When both molfile and SMILES are submitted, the molfile should win."""
        response = self.client.post(
            reverse("compounds"),
            {
                "compound-name": "4-Nitrobenzoic acid",
                "compound-description": "Molfile precedence test",
                "compound-smiles": "C",  # intentionally wrong/different
                "compound-molfile": self.VALID_MOLFILE,
            },
        )

        self.assertEqual(response.status_code, 302)
        compound_url = response.url

        c = Compound.objects.get(url=compound_url)

        # The resulting SMILES must NOT be the dummy "C" supplied via compound-smiles
        self.assertNotEqual(c.default_structure.smiles, "C")

    def test_create_compound_via_invalid_molfile_returns_error(self):
        """POSTing an invalid molfile should not create a compound and should return an error response."""
        initial_count = self.user1_default_package.compounds.count()

        response = self.client.post(
            reverse("compounds"),
            {
                "compound-name": "Bad Molfile Compound",
                "compound-description": "Should fail",
                "compound-molfile": "this is not a molfile at all",
            },
        )

        # The view should signal an error (non-2xx or a redirect to an error page, not 302 to a new compound)
        self.assertNotEqual(response.status_code, 302)
        # No new compound should have been created
        self.assertEqual(self.user1_default_package.compounds.count(), initial_count)

    def test_create_compound_via_empty_molfile_falls_back_to_smiles(self):
        """Submitting an empty molfile with a valid SMILES should fall back to SMILES."""
        response = self.client.post(
            reverse("compounds"),
            {
                "compound-name": "Fallback SMILES Compound",
                "compound-description": "Empty molfile fallback",
                "compound-smiles": "C(CCl)Cl",
                "compound-molfile": "   ",  # whitespace only
            },
        )

        self.assertEqual(response.status_code, 302)
        compound_url = response.url

        c = Compound.objects.get(url=compound_url)
        self.assertEqual(c.default_structure.smiles, "C(CCl)Cl")

    def test_create_compound_via_molfile_deduplication(self):
        """Submitting the same molfile twice should return the existing compound."""
        response1 = self.client.post(
            reverse("compounds"),
            {
                "compound-name": "4-Nitrobenzoic acid",
                "compound-description": "Molfile dedup test",
                "compound-molfile": self.VALID_MOLFILE,
            },
        )

        self.assertEqual(response1.status_code, 302)
        compound_url_1 = response1.url

        response2 = self.client.post(
            reverse("compounds"),
            {
                "compound-name": "4-Nitrobenzoic acid",
                "compound-description": "Molfile dedup test",
                "compound-molfile": self.VALID_MOLFILE,
            },
        )

        self.assertEqual(response2.status_code, 302)
        self.assertEqual(response2.url, compound_url_1)
        self.assertEqual(self.user1_default_package.compounds.count(), 1)
