from datetime import date

import tablib
from core.admin import BookResource
from core.models import Author, Book, EBook
from django.db.models import CharField, Value
from django.db.models.functions import Cast, JSONObject, TruncDate
from django.test import TestCase

from import_export.fields import Field
from import_export.resources import ModelResource


class ExportFunctionalityTest(TestCase):
    fixtures = ["author"]

    def setUp(self):
        self.resource = BookResource()
        self.book = Book.objects.create(name="Some book")
        self.dataset = tablib.Dataset(headers=["id", "name", "author_email", "price"])
        row = [self.book.pk, "Some book", "test@example.com", "10.25"]
        self.dataset.append(row)

    def test_get_export_headers(self):
        headers = self.resource.get_export_headers()
        self.assertEqual(
            headers,
            [
                "id",
                "name",
                "author",
                "author_email",
                "imported",
                "published",
                "published_time",
                "price",
                "added",
                "categories",
            ],
        )

    def test_export(self):
        with self.assertNumQueries(2):
            dataset = self.resource.export(queryset=Book.objects.all())
            self.assertEqual(len(dataset), 1)

    def test_export_with_foreign_keys(self):
        """
        Test that export() containing foreign keys doesn't generate
        extra query for every row.
        Fixes #974
        """
        author = Author.objects.create()
        self.book.author = author
        self.book.save()
        Book.objects.create(name="Second book", author=Author.objects.create())
        Book.objects.create(name="Third book", author=Author.objects.create())

        with self.assertNumQueries(3):
            dataset = self.resource.export(Book.objects.prefetch_related("categories"))
            self.assertEqual(dataset.dict[0]["author"], author.pk)
            self.assertEqual(len(dataset), 3)

    def test_export_iterable(self):
        with self.assertNumQueries(2):
            dataset = self.resource.export(queryset=list(Book.objects.all()))
            self.assertEqual(len(dataset), 1)

    def test_export_prefetch_related(self):
        with self.assertNumQueries(3):
            dataset = self.resource.export(
                queryset=Book.objects.prefetch_related("categories").all()
            )
            self.assertEqual(len(dataset), 1)

    def test_export_handles_named_queryset_parameter(self):
        class _BookResource(BookResource):
            def before_export(self, queryset, **kwargs):
                self.qs = queryset
                self.kwargs_ = kwargs

        self.resource = _BookResource()
        # when queryset is supplied, it should be passed to before_export()
        self.resource.export(queryset=Book.objects.all(), **{"a": 1})
        self.assertEqual(Book.objects.count(), len(self.resource.qs))
        self.assertEqual({"a": 1}, self.resource.kwargs_)

    def test_export_declared_field(self):
        # test that declared fields with no attribute return empty value
        # see 1874
        class EBookResource(ModelResource):
            published = Field(column_name="published")

            class Meta:
                model = EBook
                fields = ("id", "published")

        resource = EBookResource()

        self.book.published = date(1955, 4, 5)
        self.book.save()
        dataset = resource.export()
        self.assertEqual("", dataset.dict[0]["published"])

    def test_export_declared_field_custom_name(self):
        # test that declared fields with a name which differs from attribute and
        # column_name is exported
        # see 1893
        class EBookResource(ModelResource):
            auteur_name = Field(attribute="author__name", column_name="Author Name")

            class Meta:
                model = EBook
                fields = ("id", "auteur_name")

        resource = EBookResource()

        self.book.author = Author.objects.get(pk=5)
        self.book.save()
        dataset = resource.export()
        self.assertEqual("Ian Fleming", dataset.dict[0]["Author Name"])

    def test_export_declared_field_value_and_dict_key(self):
        # Test when attribute value is a dict and
        # you want to access to return an specific key value

        class EBookResource(ModelResource):
            author_name = Field(
                attribute="author_json__name", column_name="Author Name"
            )
            author_birthdate = Field(
                attribute="author_json__birthdate", column_name="Author Birthdate"
            )
            custom_attribute = Field(
                attribute="author_json__none_attribute_value__non_existentattribute",
                column_name="Custom Attribute",
            )

            class Meta:
                model = EBook
                fields = ("id", "author_name", "author_birthdate", "custom_attribute")

        self.book.author = Author.objects.get(pk=5)
        self.book.save()
        resource = EBookResource()
        author_json_value = {
            "name": "Ian Fleming",
            "birthdate": "1908-05-28",
            "none_attribute_value": None,
        }
        queryset = EBook.objects.annotate(
            author_json=JSONObject(
                name=("author__name"),
                birthdate=Cast(TruncDate("author__birthday"), output_field=CharField()),
                none_attribute_value=Value(None),  # it will treat this as a JSON null
            )
        )
        self.assertDictEqual(queryset.first().author_json, author_json_value)
        dataset = resource.export(queryset=queryset)
        self.assertEqual(dataset.dict[0]["Author Name"], "Ian Fleming")
        self.assertEqual(dataset.dict[0]["Author Birthdate"], "1908-05-28")
        self.assertEqual(dataset.dict[0]["Custom Attribute"], "")

        queryset = queryset.values("author_json")
        self.assertDictEqual(queryset.first(), {"author_json": author_json_value})
        dataset = resource.export(queryset=queryset)
        self.assertEqual(dataset.dict[0]["Author Name"], "Ian Fleming")
        self.assertEqual(dataset.dict[0]["Author Birthdate"], "1908-05-28")
        self.assertEqual(dataset.dict[0]["Custom Attribute"], "")

        author_json_value["birthdate"] = None
        author_json_value.pop("none_attribute_value")
        queryset = EBook.objects.annotate(
            author_json=JSONObject(name="author__name", birthdate=Value(None))
        )
        self.assertDictEqual(queryset.first().author_json, author_json_value)
        dataset = resource.export(queryset=queryset)
        self.assertEqual(dataset.dict[0]["Author Name"], "Ian Fleming")
        self.assertEqual(dataset.dict[0]["Author Birthdate"], "")
        self.assertEqual(dataset.dict[0]["Custom Attribute"], "")
