import datetime
import unittest

from django.contrib.gis.measure import D
from django.core.exceptions import ImproperlyConfigured
from django.test import TestCase

from haystack import connections
from haystack.manager import SearchIndexManager
from haystack.models import SearchResult
from haystack.query import (
    EmptySearchQuerySet,
    SearchQuerySet,
    ValuesListSearchQuerySet,
    ValuesSearchQuerySet,
)
from test_haystack.core.models import MockModel

from .mocks import CharPKMockSearchBackend
from .test_views import BasicAnotherMockModelSearchIndex, BasicMockModelSearchIndex

try:
    from django.contrib.gis.geos import Point

    HAVE_GDAL = True
except ImproperlyConfigured:
    HAVE_GDAL = False


class CustomManager(SearchIndexManager):
    def filter(self, *args, **kwargs):
        return self.get_search_queryset().filter(content="foo1").filter(*args, **kwargs)


class CustomMockModelIndexWithObjectsManager(BasicMockModelSearchIndex):
    objects = CustomManager()


class CustomMockModelIndexWithAnotherManager(BasicMockModelSearchIndex):
    another = CustomManager()


class ManagerTestCase(TestCase):
    fixtures = ["bulk_data.json"]

    def setUp(self):
        super().setUp()

        self.search_index = BasicMockModelSearchIndex
        # Update the "index".
        backend = connections["default"].get_backend()
        backend.clear()
        backend.update(self.search_index(), MockModel.objects.all())
        ui = connections["default"].get_unified_index()
        ui.build([BasicMockModelSearchIndex(), BasicAnotherMockModelSearchIndex()])

        self.search_queryset = BasicMockModelSearchIndex.objects.all()

    def test_queryset(self):
        self.assertTrue(isinstance(self.search_queryset, SearchQuerySet))

    def test_none(self):
        self.assertTrue(
            isinstance(self.search_index.objects.none(), EmptySearchQuerySet)
        )

    def test_filter(self):
        sqs = self.search_index.objects.filter(content="foo")
        self.assertTrue(isinstance(sqs, SearchQuerySet))
        self.assertEqual(len(sqs.query.query_filter), 1)

    def test_exclude(self):
        sqs = self.search_index.objects.exclude(content="foo")
        self.assertTrue(isinstance(sqs, SearchQuerySet))
        self.assertEqual(len(sqs.query.query_filter), 1)

    def test_filter_and(self):
        sqs = self.search_index.objects.filter_and(content="foo")
        self.assertTrue(isinstance(sqs, SearchQuerySet))
        self.assertEqual(sqs.query.query_filter.connector, "AND")

    def test_filter_or(self):
        sqs = self.search_index.objects.filter_or(content="foo")
        self.assertTrue(isinstance(sqs, SearchQuerySet))
        self.assertEqual(sqs.query.query_filter.connector, "OR")

    def test_order_by(self):
        sqs = self.search_index.objects.order_by("foo")
        self.assertTrue(isinstance(sqs, SearchQuerySet))
        self.assertTrue("foo" in sqs.query.order_by)

    @unittest.skipUnless(HAVE_GDAL, "Requires gdal library")
    def test_order_by_distance(self):
        p = Point(1.23, 4.56)
        sqs = self.search_index.objects.distance("location", p).order_by("distance")
        self.assertTrue(isinstance(sqs, SearchQuerySet))

        params = sqs.query.build_params()

        self.assertIn("distance_point", params)
        self.assertDictEqual(
            params["distance_point"], {"field": "location", "point": p}
        )
        self.assertTupleEqual(params["distance_point"]["point"].coords, (1.23, 4.56))

        self.assertListEqual(params["sort_by"], ["distance"])

    def test_highlight(self):
        sqs = self.search_index.objects.highlight()
        self.assertEqual(sqs.query.highlight, True)

    def test_boost(self):
        sqs = self.search_index.objects.boost("foo", 10)
        self.assertTrue(isinstance(sqs, SearchQuerySet))
        self.assertEqual(len(sqs.query.boost.keys()), 1)

    def test_facets(self):
        sqs = self.search_index.objects.facet("foo")
        self.assertTrue(isinstance(sqs, SearchQuerySet))
        self.assertEqual(len(sqs.query.facets), 1)

    @unittest.skipUnless(HAVE_GDAL, "Requires gdal library")
    def test_within(self):
        # This is a meaningless query but we're just confirming that the manager updates the parameters here:
        p1 = Point(-90, -90)
        p2 = Point(90, 90)
        sqs = self.search_index.objects.within("location", p1, p2)
        self.assertTrue(isinstance(sqs, SearchQuerySet))

        params = sqs.query.build_params()

        self.assertIn("within", params)
        self.assertDictEqual(
            params["within"], {"field": "location", "point_1": p1, "point_2": p2}
        )

    @unittest.skipUnless(HAVE_GDAL, "Requires gdal library")
    def test_dwithin(self):
        p = Point(0, 0)
        distance = D(mi=500)
        sqs = self.search_index.objects.dwithin("location", p, distance)
        self.assertTrue(isinstance(sqs, SearchQuerySet))

        params = sqs.query.build_params()

        self.assertIn("dwithin", params)
        self.assertDictEqual(
            params["dwithin"], {"field": "location", "point": p, "distance": distance}
        )

    @unittest.skipUnless(HAVE_GDAL, "Requires gdal library")
    def test_distance(self):
        p = Point(0, 0)
        sqs = self.search_index.objects.distance("location", p)
        self.assertTrue(isinstance(sqs, SearchQuerySet))

        params = sqs.query.build_params()
        self.assertIn("distance_point", params)
        self.assertDictEqual(
            params["distance_point"], {"field": "location", "point": p}
        )

    def test_date_facets(self):
        sqs = self.search_index.objects.date_facet(
            "foo",
            start_date=datetime.date(2008, 2, 25),
            end_date=datetime.date(2009, 2, 25),
            gap_by="month",
        )
        self.assertTrue(isinstance(sqs, SearchQuerySet))
        self.assertEqual(len(sqs.query.date_facets), 1)

    def test_query_facets(self):
        sqs = self.search_index.objects.query_facet("foo", "[bar TO *]")
        self.assertTrue(isinstance(sqs, SearchQuerySet))
        self.assertEqual(len(sqs.query.query_facets), 1)

    def test_narrow(self):
        sqs = self.search_index.objects.narrow("content:foo")
        self.assertTrue(isinstance(sqs, SearchQuerySet))
        self.assertSetEqual(set(["content:foo"]), sqs.query.narrow_queries)

    def test_raw_search(self):
        self.assertEqual(len(self.search_index.objects.raw_search("foo")), 23)

    def test_load_all(self):
        # Models with character primary keys.
        sqs = self.search_index.objects.all()
        sqs.query.backend = CharPKMockSearchBackend("charpk")
        results = sqs.load_all().all()
        self.assertEqual(len(results._result_cache), 0)

    def test_auto_query(self):
        sqs = self.search_index.objects.auto_query("test search -stuff")
        self.assertTrue(isinstance(sqs, SearchQuerySet))
        self.assertEqual(
            repr(sqs.query.query_filter),
            "<SQ: AND content__content=test search -stuff>",
        )

        # With keyword argument
        sqs = self.search_index.objects.auto_query(
            "test search -stuff", fieldname="title"
        )
        self.assertTrue(isinstance(sqs, SearchQuerySet))
        self.assertEqual(
            repr(sqs.query.query_filter), "<SQ: AND title__content=test search -stuff>"
        )

    def test_autocomplete(self):
        # Not implemented
        pass

    def test_count(self):
        self.assertEqual(SearchQuerySet().count(), 23)
        self.assertEqual(self.search_index.objects.count(), 23)

    def test_best_match(self):
        self.assertTrue(
            isinstance(self.search_index.objects.best_match(), SearchResult)
        )

    def test_latest(self):
        self.assertTrue(
            isinstance(self.search_index.objects.latest("pub_date"), SearchResult)
        )

    def test_more_like_this(self):
        mock = MockModel()
        mock.id = 1

        self.assertEqual(len(self.search_index.objects.more_like_this(mock)), 23)

    def test_facet_counts(self):
        self.assertEqual(self.search_index.objects.facet_counts(), {})

    def spelling_suggestion(self):
        # Test the case where spelling support is disabled.
        sqs = self.search_index.objects.filter(content="Indx")
        self.assertEqual(sqs.spelling_suggestion(), None)
        self.assertEqual(sqs.spelling_suggestion(preferred_query=None), None)

    def test_values(self):
        sqs = self.search_index.objects.auto_query("test").values("id")
        self.assertIsInstance(sqs, ValuesSearchQuerySet)

    def test_valueslist(self):
        sqs = self.search_index.objects.auto_query("test").values_list("id")
        self.assertIsInstance(sqs, ValuesListSearchQuerySet)


class CustomManagerTestCase(TestCase):
    fixtures = ["bulk_data.json"]

    def setUp(self):
        super().setUp()

        self.search_index_1 = CustomMockModelIndexWithObjectsManager
        self.search_index_2 = CustomMockModelIndexWithAnotherManager

    def test_filter_object_manager(self):
        sqs = self.search_index_1.objects.filter(content="foo")
        self.assertTrue(isinstance(sqs, SearchQuerySet))
        self.assertEqual(len(sqs.query.query_filter), 2)

    def test_filter_another_manager(self):
        sqs = self.search_index_2.another.filter(content="foo")
        self.assertTrue(isinstance(sqs, SearchQuerySet))
        self.assertEqual(len(sqs.query.query_filter), 2)
