import datetime

from django.core.management import call_command
from django.urls import reverse
from rest_framework import status
from rest_framework.test import APITestCase

from resources_portal.management.commands.populate_dev_database import populate_dev_database
from resources_portal.models import Material, Organization, User


class SearchMaterialsEndpointTestCase(APITestCase):
    """
    Tests /search/materials operations.
    """

    @classmethod
    def setUpClass(cls):
        super(SearchMaterialsEndpointTestCase, cls).setUpClass()

        populate_dev_database()

        # Put newly created materials in the search index
        call_command("search_index", "-f", "--rebuild")

        cls.primary_prof = User.objects.get(username="PrimaryProf")
        cls.secondary_prof = User.objects.get(username="SecondaryProf")
        cls.post_doc = User.objects.get(username="PostDoc")

        cls.primary_lab = Organization.objects.get(name="PrimaryLab")

        cls.material1 = Material.objects.get(title="Melanoma Reduction Plasmid")
        cls.material2 = Material.objects.get(title="Allele Extraction Protocol")

    @classmethod
    def tearDownClass(cls):
        super(SearchMaterialsEndpointTestCase, cls).tearDownClass()

        # Rebuild search index with what's actaully in the django database
        call_command("search_index", "-f", "--rebuild")

    def test_search_for_title_finds_a_given_material(self):
        self.client.force_authenticate(user=self.primary_prof)

        search_url = reverse("search-materials-list") + "?search=" + self.material1.title

        response = self.client.get(search_url)
        self.assertEqual(response.status_code, status.HTTP_200_OK)

        first_result_id = int(response.json()["results"][0]["id"])

        self.assertEqual(first_result_id, self.material1.id)

    def test_filter_on_organization_retrieves_all_organization_materials(self):
        # Archive one material to make sure it goes to the bottom of the list.
        archived_material = Material.objects.first()
        archived_material.is_archived = True
        archived_material.save()

        self.client.force_authenticate(user=self.primary_prof)

        search_url = (
            reverse("search-materials-list")
            + "?organization="
            + self.primary_lab.name
            + "&limit=25"
        )

        response = self.client.get(search_url)
        self.assertEqual(response.status_code, status.HTTP_200_OK)

        response_json = response.json()
        material_count = int(response_json["count"])

        # Make sure archived materials are last:
        self.assertEqual(response_json["results"][-1]["id"], archived_material.id)

        material_titles = []
        for material in response_json["results"]:
            material_titles.append(material["title"])

        self.assertEqual(material_count, len(self.primary_lab.materials.all()))

        for title in material_titles:
            self.assertTrue(
                Material.objects.filter(title=title, organization=self.primary_lab).exists()
            )

    def test_filter_on_category_retrieves_all_materials_of_a_given_category(self):
        self.client.force_authenticate(user=self.primary_prof)

        search_url = reverse("search-materials-list") + "?category=" + "MODEL_ORGANISM"

        response = self.client.get(search_url)
        self.assertEqual(response.status_code, status.HTTP_200_OK)

        material_count = int(response.json()["count"])

        material_titles = []
        for material in response.json()["results"]:
            material_titles.append(material["title"])

        self.assertEqual(material_count, len(Material.objects.filter(category="MODEL_ORGANISM")))

        for title in material_titles:
            self.assertTrue(
                Material.objects.filter(title=title, category="MODEL_ORGANISM").exists()
            )

    def test_filter_on_organisms_retrieves_all_materials_with_one_organism(self):
        self.client.force_authenticate(user=self.primary_prof)

        # Search with one organism name
        search_url = reverse("search-materials-list") + "?organisms=" + "danio rerio"

        response = self.client.get(search_url)
        self.assertEqual(response.status_code, status.HTTP_200_OK)

        organism_count = int(response.json()["count"])

        material_titles = []
        for material in response.json()["results"]:
            material_titles.append(material["title"])

        database_organism_count = 0
        database_titles = []
        for material in Material.objects.all():
            if material.organisms:
                if "Danio rerio" in material.organisms:
                    database_organism_count += 1
                    database_titles.append(material.title)

        self.assertEqual(organism_count, database_organism_count)

        for title in material_titles:
            self.assertTrue(title in database_titles)

    def test_filter_on_organisms_retrieves_all_materials_with_multiple_organisms(self):
        self.client.force_authenticate(user=self.primary_prof)

        # Search with one organism name
        search_url = (
            reverse("search-materials-list")
            + "?organisms="
            + "danio rerio"
            + "&organisms="
            + "mus musculus"
        )

        response = self.client.get(search_url)
        self.assertEqual(response.status_code, status.HTTP_200_OK)

        organism_count = int(response.json()["count"])

        material_titles = []
        for material in response.json()["results"]:
            material_titles.append(material["title"])

        database_organism_count = 0
        database_titles = []
        for material in Material.objects.all():
            if material.organisms:
                if ("Danio rerio" in material.organisms) or ("Mus musculus" in material.organisms):
                    database_organism_count += 1
                    database_titles.append(material.title)

        self.assertEqual(organism_count, database_organism_count)

        for title in material_titles:
            self.assertTrue(title in database_titles)

    def test_ordering_on_updated_at_succeeds(self):
        self.client.force_authenticate(user=self.primary_prof)

        search_url = reverse("search-materials-list") + "?ordering=" + "updated_at"

        response = self.client.get(search_url)
        self.assertEqual(response.status_code, status.HTTP_200_OK)

        material_dates = []
        for material in response.json()["results"]:
            date = datetime.datetime.strptime(
                material["updated_at"], "%Y-%m-%dT%H:%M:%S.%f%z"
            ).date()
            material_dates.append(date)

        self.assertEqual(material_dates, sorted(material_dates))

    def test_combine_search_and_filter_and_ordering_succeeds(self):
        self.client.force_authenticate(user=self.primary_prof)

        search_url = (
            reverse("search-materials-list")
            + "?search=MODEL_ORGANISM"
            + "ordering=updated_at"
            + "has_pre_print=true"
        )

        response = self.client.get(search_url)
        self.assertEqual(response.status_code, status.HTTP_200_OK)

        material_dates = []
        material_titles = []
        for material in response.json()["results"]:
            material_titles.append(material["title"])

            date = datetime.datetime.strptime(
                material["updated_at"], "%Y-%m-%dT%H:%M:%S.%f%z"
            ).date()
            material_dates.append(date)

        self.assertEqual(material_dates, sorted(material_dates))

        for title in material_titles:
            self.assertTrue(
                Material.objects.filter(
                    title=title, category="MODEL_ORGANISM", has_pre_print=True
                ).exists()
            )

    def test_facets_return_number_of_materials(self):
        self.client.force_authenticate(user=self.primary_prof)

        # Search with no params
        search_url = reverse("search-materials-list")

        response = self.client.get(search_url)
        self.assertEqual(response.status_code, status.HTTP_200_OK)

        model_organism_count = int(response.json()["facets"]["category"]["MODEL_ORGANISM"])

        self.assertEqual(
            model_organism_count, len(Material.objects.filter(category="MODEL_ORGANISM"))
        )

        # Search for only danio rerio organisms
        search_url = reverse("search-materials-list") + "?search=danio rerio"

        response = self.client.get(search_url)
        self.assertEqual(response.status_code, status.HTTP_200_OK)

        model_organism_count = int(response.json()["facets"]["category"]["MODEL_ORGANISM"])

        database_count = 0
        for material in Material.objects.all():
            if material.organisms:
                if ("Danio rerio" in material.organisms) and (
                    material.category == "MODEL_ORGANISM"
                ):
                    database_count += 1

        self.assertEqual(model_organism_count, database_count)

    def test_empty_search_returns_no_results(self):
        self.client.force_authenticate(user=self.primary_prof)

        search_url = reverse("search-materials-list") + "?search="

        response = self.client.get(search_url)
        self.assertEqual(response.status_code, status.HTTP_200_OK)

        material_count = int(response.json()["count"])

        self.assertEqual(material_count, 0)


class SearchUsersEndpointTestCase(APITestCase):
    """
    Tests /search/users operations.
    """

    @classmethod
    def setUpClass(cls):
        super(SearchUsersEndpointTestCase, cls).setUpClass()

        populate_dev_database()

        # Put newly created materials in the search index
        call_command("search_index", "-f", "--rebuild")

        cls.primary_prof = User.objects.get(username="PrimaryProf")

    @classmethod
    def tearDownClass(cls):
        super(SearchUsersEndpointTestCase, cls).tearDownClass()

        # Rebuild search index with what's actaully in the django database
        call_command("search_index", "-f", "--rebuild")

    def test_search_for_name_returns_given_user(self):
        self.client.force_authenticate(user=self.primary_prof)

        search_url = (
            reverse("search-users-list")
            + "?search="
            + self.primary_prof.first_name
            + " "
            + self.primary_prof.last_name
        )

        response = self.client.get(search_url)
        self.assertEqual(response.status_code, status.HTTP_200_OK)

        first_result_id = response.json()["results"][0]["id"]

        self.assertEqual(first_result_id, str(self.primary_prof.id))

    def test_order_by_published_name_succeeds(self):
        self.client.force_authenticate(user=self.primary_prof)

        search_url = reverse("search-users-list") + "?ordering=published_name"

        response = self.client.get(search_url)
        self.assertEqual(response.status_code, status.HTTP_200_OK)

        user_published_names = []
        for user in response.json()["results"]:
            if user["published_name"]:
                user_published_names.append(user["published_name"])

        self.assertEqual(user_published_names, sorted(user_published_names))

    def test_empty_search_returns_no_results(self):
        self.client.force_authenticate(user=self.primary_prof)

        search_url = reverse("search-users-list") + "?search="

        response = self.client.get(search_url)
        self.assertEqual(response.status_code, status.HTTP_200_OK)

        user_count = int(response.json()["count"])

        self.assertEqual(user_count, 0)


class SearchOrganizationsEndpointTestCase(APITestCase):
    """
    Tests /search/organizations operations.
    """

    @classmethod
    def setUpClass(cls):
        super(SearchOrganizationsEndpointTestCase, cls).setUpClass()

        populate_dev_database()

        # Put newly created materials in the search index
        call_command("search_index", "-f", "--rebuild")

        cls.primary_prof = User.objects.get(username="PrimaryProf")
        cls.primary_lab = Organization.objects.get(name="PrimaryLab")

    @classmethod
    def tearDownClass(cls):
        super(SearchOrganizationsEndpointTestCase, cls).tearDownClass()

        # Rebuild search index with what's actaully in the django database
        call_command("search_index", "-f", "--rebuild")

    def test_search_for_organization_name_returns_given_organization(self):
        self.client.force_authenticate(user=self.primary_prof)

        search_url = reverse("search-organizations-list") + "?search=" + self.primary_lab.name

        response = self.client.get(search_url)
        self.assertEqual(response.status_code, status.HTTP_200_OK)

        first_result_id = int(response.json()["results"][0]["id"])

        self.assertEqual(first_result_id, self.primary_lab.id)

    def test_search_for_owner_attribute_returns_related_organizations(self):
        self.client.force_authenticate(user=self.primary_prof)

        search_url = reverse("search-organizations-list") + "?search=" + self.primary_prof.email

        response = self.client.get(search_url)
        self.assertEqual(response.status_code, status.HTTP_200_OK)

        organization_count = int(response.json()["count"])

        organization_names = []
        for org in response.json()["results"]:
            organization_names.append(org["name"])

        self.assertEqual(
            organization_count, len(Organization.objects.filter(owner=self.primary_prof))
        )

        for name in organization_names:
            self.assertTrue(
                Organization.objects.filter(name=name, owner=self.primary_prof).exists()
            )

    def test_ordering_on_updated_at_succeeds(self):
        self.client.force_authenticate(user=self.primary_prof)

        search_url = reverse("search-organizations-list") + "?ordering=" + "updated_at"

        response = self.client.get(search_url)
        self.assertEqual(response.status_code, status.HTTP_200_OK)

        organization_dates = []
        for org in response.json()["results"]:
            date = datetime.datetime.strptime(org["updated_at"], "%Y-%m-%dT%H:%M:%S.%f%z").date()
            organization_dates.append(date)

        self.assertEqual(organization_dates, sorted(organization_dates))

[((21, 8, 21, 31), 'resources_portal.management.commands.populate_dev_database.populate_dev_database', 'populate_dev_database', ({}, {}), '()', False, 'from resources_portal.management.commands.populate_dev_database import populate_dev_database\n'), ((24, 8, 24, 55), 'django.core.management.call_command', 'call_command', ({(24, 21, 24, 35): '"""search_index"""', (24, 37, 24, 41): '"""-f"""', (24, 43, 24, 54): '"""--rebuild"""'}, {}), "('search_index', '-f', '--rebuild')", False, 'from django.core.management import call_command\n'), ((26, 27, 26, 67), 'resources_portal.models.User.objects.get', 'User.objects.get', (), '', False, 'from resources_portal.models import Material, Organization, User\n'), ((27, 29, 27, 71), 'resources_portal.models.User.objects.get', 'User.objects.get', (), '', False, 'from resources_portal.models import Material, Organization, User\n'), ((28, 23, 28, 59), 'resources_portal.models.User.objects.get', 'User.objects.get', (), '', False, 'from resources_portal.models import Material, Organization, User\n'), ((30, 26, 30, 69), 'resources_portal.models.Organization.objects.get', 'Organization.objects.get', (), '', False, 'from resources_portal.models import Material, Organization, User\n'), ((32, 24, 32, 80), 'resources_portal.models.Material.objects.get', 'Material.objects.get', (), '', False, 'from resources_portal.models import Material, Organization, User\n'), ((33, 24, 33, 80), 'resources_portal.models.Material.objects.get', 'Material.objects.get', (), '', False, 'from resources_portal.models import Material, Organization, User\n'), ((40, 8, 40, 55), 'django.core.management.call_command', 'call_command', ({(40, 21, 40, 35): '"""search_index"""', (40, 37, 40, 41): '"""-f"""', (40, 43, 40, 54): '"""--rebuild"""'}, {}), "('search_index', '-f', '--rebuild')", False, 'from django.core.management import call_command\n'), ((56, 28, 56, 52), 'resources_portal.models.Material.objects.first', 'Material.objects.first', ({}, {}), '()', False, 'from resources_portal.models import Material, Organization, User\n'), ((127, 24, 127, 46), 'resources_portal.models.Material.objects.all', 'Material.objects.all', ({}, {}), '()', False, 'from resources_portal.models import Material, Organization, User\n'), ((161, 24, 161, 46), 'resources_portal.models.Material.objects.all', 'Material.objects.all', ({}, {}), '()', False, 'from resources_portal.models import Material, Organization, User\n'), ((225, 21, 225, 53), 'django.urls.reverse', 'reverse', ({(225, 29, 225, 52): '"""search-materials-list"""'}, {}), "('search-materials-list')", False, 'from django.urls import reverse\n'), ((245, 24, 245, 46), 'resources_portal.models.Material.objects.all', 'Material.objects.all', ({}, {}), '()', False, 'from resources_portal.models import Material, Organization, User\n'), ((276, 8, 276, 31), 'resources_portal.management.commands.populate_dev_database.populate_dev_database', 'populate_dev_database', ({}, {}), '()', False, 'from resources_portal.management.commands.populate_dev_database import populate_dev_database\n'), ((279, 8, 279, 55), 'django.core.management.call_command', 'call_command', ({(279, 21, 279, 35): '"""search_index"""', (279, 37, 279, 41): '"""-f"""', (279, 43, 279, 54): '"""--rebuild"""'}, {}), "('search_index', '-f', '--rebuild')", False, 'from django.core.management import call_command\n'), ((281, 27, 281, 67), 'resources_portal.models.User.objects.get', 'User.objects.get', (), '', False, 'from resources_portal.models import Material, Organization, User\n'), ((288, 8, 288, 55), 'django.core.management.call_command', 'call_command', ({(288, 21, 288, 35): '"""search_index"""', (288, 37, 288, 41): '"""-f"""', (288, 43, 288, 54): '"""--rebuild"""'}, {}), "('search_index', '-f', '--rebuild')", False, 'from django.core.management import call_command\n'), ((345, 8, 345, 31), 'resources_portal.management.commands.populate_dev_database.populate_dev_database', 'populate_dev_database', ({}, {}), '()', False, 'from resources_portal.management.commands.populate_dev_database import populate_dev_database\n'), ((348, 8, 348, 55), 'django.core.management.call_command', 'call_command', ({(348, 21, 348, 35): '"""search_index"""', (348, 37, 348, 41): '"""-f"""', (348, 43, 348, 54): '"""--rebuild"""'}, {}), "('search_index', '-f', '--rebuild')", False, 'from django.core.management import call_command\n'), ((350, 27, 350, 67), 'resources_portal.models.User.objects.get', 'User.objects.get', (), '', False, 'from resources_portal.models import Material, Organization, User\n'), ((351, 26, 351, 69), 'resources_portal.models.Organization.objects.get', 'Organization.objects.get', (), '', False, 'from resources_portal.models import Material, Organization, User\n'), ((358, 8, 358, 55), 'django.core.management.call_command', 'call_command', ({(358, 21, 358, 35): '"""search_index"""', (358, 37, 358, 41): '"""-f"""', (358, 43, 358, 54): '"""--rebuild"""'}, {}), "('search_index', '-f', '--rebuild')", False, 'from django.core.management import call_command\n'), ((237, 21, 237, 53), 'django.urls.reverse', 'reverse', ({(237, 29, 237, 52): '"""search-materials-list"""'}, {}), "('search-materials-list')", False, 'from django.urls import reverse\n'), ((257, 21, 257, 53), 'django.urls.reverse', 'reverse', ({(257, 29, 257, 52): '"""search-materials-list"""'}, {}), "('search-materials-list')", False, 'from django.urls import reverse\n'), ((311, 21, 311, 49), 'django.urls.reverse', 'reverse', ({(311, 29, 311, 48): '"""search-users-list"""'}, {}), "('search-users-list')", False, 'from django.urls import reverse\n'), ((326, 21, 326, 49), 'django.urls.reverse', 'reverse', ({(326, 29, 326, 48): '"""search-users-list"""'}, {}), "('search-users-list')", False, 'from django.urls import reverse\n'), ((45, 21, 45, 53), 'django.urls.reverse', 'reverse', ({(45, 29, 45, 52): '"""search-materials-list"""'}, {}), "('search-materials-list')", False, 'from django.urls import reverse\n'), ((92, 21, 92, 53), 'django.urls.reverse', 'reverse', ({(92, 29, 92, 52): '"""search-materials-list"""'}, {}), "('search-materials-list')", False, 'from django.urls import reverse\n'), ((103, 45, 103, 95), 'resources_portal.models.Material.objects.filter', 'Material.objects.filter', (), '', False, 'from resources_portal.models import Material, Organization, User\n'), ((114, 21, 114, 53), 'django.urls.reverse', 'reverse', ({(114, 29, 114, 52): '"""search-materials-list"""'}, {}), "('search-materials-list')", False, 'from django.urls import reverse\n'), ((175, 21, 175, 53), 'django.urls.reverse', 'reverse', ({(175, 29, 175, 52): '"""search-materials-list"""'}, {}), "('search-materials-list')", False, 'from django.urls import reverse\n'), ((233, 38, 233, 88), 'resources_portal.models.Material.objects.filter', 'Material.objects.filter', (), '', False, 'from resources_portal.models import Material, Organization, User\n'), ((363, 21, 363, 57), 'django.urls.reverse', 'reverse', ({(363, 29, 363, 56): '"""search-organizations-list"""'}, {}), "('search-organizations-list')", False, 'from django.urls import reverse\n'), ((375, 21, 375, 57), 'django.urls.reverse', 'reverse', ({(375, 29, 375, 56): '"""search-organizations-list"""'}, {}), "('search-organizations-list')", False, 'from django.urls import reverse\n'), ((387, 36, 387, 88), 'resources_portal.models.Organization.objects.filter', 'Organization.objects.filter', (), '', False, 'from resources_portal.models import Material, Organization, User\n'), ((398, 21, 398, 57), 'django.urls.reverse', 'reverse', ({(398, 29, 398, 56): '"""search-organizations-list"""'}, {}), "('search-organizations-list')", False, 'from django.urls import reverse\n'), ((63, 12, 63, 44), 'django.urls.reverse', 'reverse', ({(63, 20, 63, 43): '"""search-materials-list"""'}, {}), "('search-materials-list')", False, 'from django.urls import reverse\n'), ((182, 19, 184, 13), 'datetime.datetime.strptime', 'datetime.datetime.strptime', ({(183, 16, 183, 38): "material['updated_at']", (183, 40, 183, 64): '"""%Y-%m-%dT%H:%M:%S.%f%z"""'}, {}), "(material['updated_at'], '%Y-%m-%dT%H:%M:%S.%f%z')", False, 'import datetime\n'), ((193, 12, 193, 44), 'django.urls.reverse', 'reverse', ({(193, 20, 193, 43): '"""search-materials-list"""'}, {}), "('search-materials-list')", False, 'from django.urls import reverse\n'), ((207, 19, 209, 13), 'datetime.datetime.strptime', 'datetime.datetime.strptime', ({(208, 16, 208, 38): "material['updated_at']", (208, 40, 208, 64): '"""%Y-%m-%dT%H:%M:%S.%f%z"""'}, {}), "(material['updated_at'], '%Y-%m-%dT%H:%M:%S.%f%z')", False, 'import datetime\n'), ((405, 19, 405, 90), 'datetime.datetime.strptime', 'datetime.datetime.strptime', ({(405, 46, 405, 63): "org['updated_at']", (405, 65, 405, 89): '"""%Y-%m-%dT%H:%M:%S.%f%z"""'}, {}), "(org['updated_at'], '%Y-%m-%dT%H:%M:%S.%f%z')", False, 'import datetime\n'), ((86, 16, 86, 83), 'resources_portal.models.Material.objects.filter', 'Material.objects.filter', (), '', False, 'from resources_portal.models import Material, Organization, User\n'), ((107, 16, 107, 79), 'resources_portal.models.Material.objects.filter', 'Material.objects.filter', (), '', False, 'from resources_portal.models import Material, Organization, User\n'), ((143, 12, 143, 44), 'django.urls.reverse', 'reverse', ({(143, 20, 143, 43): '"""search-materials-list"""'}, {}), "('search-materials-list')", False, 'from django.urls import reverse\n'), ((216, 16, 218, 17), 'resources_portal.models.Material.objects.filter', 'Material.objects.filter', (), '', False, 'from resources_portal.models import Material, Organization, User\n'), ((294, 12, 294, 40), 'django.urls.reverse', 'reverse', ({(294, 20, 294, 39): '"""search-users-list"""'}, {}), "('search-users-list')", False, 'from django.urls import reverse\n'), ((392, 16, 392, 79), 'resources_portal.models.Organization.objects.filter', 'Organization.objects.filter', (), '', False, 'from resources_portal.models import Material, Organization, User\n')]