import unittest
import uuid
import warnings
from datetime import datetime, timedelta

import django
from django.apps import apps
from django.conf import settings
from django.contrib.auth import get_user_model
from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist
from django.core.files.base import ContentFile
from django.db import IntegrityError, models
from django.db.models.fields.proxy import OrderWrt
from django.test import TestCase, override_settings
from django.urls import reverse
from django.utils import timezone

from simple_history import register
from simple_history.exceptions import RelatedNameConflictError
from simple_history.models import (
    SIMPLE_HISTORY_REVERSE_ATTR_NAME,
    HistoricalRecords,
    ModelChange,
    is_historic,
    to_historic,
)
from simple_history.signals import pre_create_historical_record
from simple_history.tests.custom_user.models import CustomUser
from simple_history.tests.tests.utils import (
    database_router_override_settings,
    database_router_override_settings_history_in_diff_db,
    middleware_override_settings,
)
from simple_history.utils import get_history_model_for_model, update_change_reason

from ..external.models import (
    ExternalModel,
    ExternalModelRegistered,
    ExternalModelWithCustomUserIdField,
)
from ..models import (
    AbstractBase,
    AdminProfile,
    BasePlace,
    Book,
    Bookcase,
    BucketData,
    BucketDataRegisterChangedBy,
    BucketMember,
    CharFieldChangeReasonModel,
    CharFieldFileModel,
    Choice,
    City,
    ConcreteAttr,
    ConcreteExternal,
    ConcreteUtil,
    Contact,
    ContactRegister,
    Country,
    CustomManagerNameModel,
    DefaultTextFieldChangeReasonModel,
    Document,
    Employee,
    ExternalModelSpecifiedWithAppParam,
    ExternalModelWithAppLabel,
    FileModel,
    ForeignKeyToSelfModel,
    HistoricalChoice,
    HistoricalCustomFKError,
    HistoricalPoll,
    HistoricalPollWithHistoricalIPAddress,
    HistoricalState,
    InheritedRestaurant,
    Library,
    ManyToManyModelOther,
    ModelWithExcludedManyToMany,
    ModelWithFkToModelWithHistoryUsingBaseModelDb,
    ModelWithHistoryInDifferentDb,
    ModelWithHistoryUsingBaseModelDb,
    ModelWithMultipleNoDBIndex,
    ModelWithSingleNoDBIndexUnique,
    MultiOneToOne,
    MyOverrideModelNameRegisterMethod1,
    OverrideModelNameAsCallable,
    OverrideModelNameUsingBaseModel1,
    Person,
    Place,
    Poll,
    PollInfo,
    PollWithExcludedFieldsWithDefaults,
    PollWithExcludedFKField,
    PollWithExcludeFields,
    PollWithHistoricalIPAddress,
    PollWithNonEditableField,
    Province,
    Restaurant,
    SelfFK,
    Series,
    SeriesWork,
    State,
    Street,
    Temperature,
    TestHistoricParticipanToHistoricOrganization,
    TestHistoricParticipantToOrganization,
    TestOrganization,
    TestOrganizationWithHistory,
    TestParticipantToHistoricOrganization,
    UnicodeVerboseName,
    UnicodeVerboseNamePlural,
    UserTextFieldChangeReasonModel,
    UUIDDefaultModel,
    UUIDModel,
    WaterLevel,
)

get_model = apps.get_model
User = get_user_model()
today = datetime(3021, 1, 1, 10, 0)
tomorrow = today + timedelta(days=1)
yesterday = today - timedelta(days=1)


def get_fake_file(filename):
    fake_file = ContentFile("file data")
    fake_file.name = filename
    return fake_file


class HistoricalRecordsTest(TestCase):
    def assertDatetimesEqual(self, time1, time2):
        self.assertAlmostEqual(time1, time2, delta=timedelta(seconds=2))

    def assertRecordValues(self, record, klass, values_dict):
        for key, value in values_dict.items():
            self.assertEqual(getattr(record, key), value)
        self.assertEqual(record.history_object.__class__, klass)
        for key, value in values_dict.items():
            if key not in ["history_type", "history_change_reason"]:
                self.assertEqual(getattr(record.history_object, key), value)

    def test_create(self):
        p = Poll(question="what's up?", pub_date=today)
        p.save()
        (record,) = p.history.all()
        self.assertRecordValues(
            record,
            Poll,
            {
                "question": "what's up?",
                "pub_date": today,
                "id": p.id,
                "history_type": "+",
            },
        )
        self.assertDatetimesEqual(record.history_date, datetime.now())

    def test_update(self):
        Poll.objects.create(question="what's up?", pub_date=today)
        p = Poll.objects.get()
        p.pub_date = tomorrow
        p.save()
        update_change_reason(p, "future poll")
        update_record, create_record = p.history.all()
        self.assertRecordValues(
            create_record,
            Poll,
            {
                "question": "what's up?",
                "pub_date": today,
                "id": p.id,
                "history_change_reason": None,
                "history_type": "+",
            },
        )
        self.assertRecordValues(
            update_record,
            Poll,
            {
                "question": "what's up?",
                "pub_date": tomorrow,
                "id": p.id,
                "history_change_reason": "future poll",
                "history_type": "~",
            },
        )
        self.assertDatetimesEqual(update_record.history_date, datetime.now())

    def test_delete_verify_change_reason_implicitly(self):
        p = Poll.objects.create(question="what's up?", pub_date=today)
        poll_id = p.id
        p._change_reason = "wrongEntry"
        p.delete()
        delete_record, create_record = Poll.history.all()
        self.assertRecordValues(
            create_record,
            Poll,
            {
                "question": "what's up?",
                "pub_date": today,
                "id": poll_id,
                "history_change_reason": None,
                "history_type": "+",
            },
        )
        self.assertRecordValues(
            delete_record,
            Poll,
            {
                "question": "what's up?",
                "pub_date": today,
                "id": poll_id,
                "history_change_reason": "wrongEntry",
                "history_type": "-",
            },
        )

    def test_delete_verify_change_reason_explicity(self):
        p = Poll.objects.create(question="what's up?", pub_date=today)
        poll_id = p.id
        p.delete()
        update_change_reason(p, "wrongEntry")
        delete_record, create_record = Poll.history.all()
        self.assertRecordValues(
            create_record,
            Poll,
            {
                "question": "what's up?",
                "pub_date": today,
                "id": poll_id,
                "history_change_reason": None,
                "history_type": "+",
            },
        )
        self.assertRecordValues(
            delete_record,
            Poll,
            {
                "question": "what's up?",
                "pub_date": today,
                "id": poll_id,
                "history_change_reason": "wrongEntry",
                "history_type": "-",
            },
        )

    def test_cascade_delete_history(self):
        thames = WaterLevel.objects.create(waters="Thames", level=2.5, date=today)
        nile = WaterLevel.objects.create(waters="Nile", level=2.5, date=today)

        self.assertEqual(len(thames.history.all()), 1)
        self.assertEqual(len(nile.history.all()), 1)

        nile.delete()

        self.assertEqual(len(thames.history.all()), 1)
        self.assertEqual(len(nile.history.all()), 0)

    def test_save_without_historical_record(self):
        pizza_place = Restaurant.objects.create(name="Pizza Place", rating=3)
        pizza_place.rating = 4
        pizza_place.save_without_historical_record()
        pizza_place.rating = 6
        pizza_place.save()
        update_record, create_record = Restaurant.updates.all()
        self.assertRecordValues(
            create_record,
            Restaurant,
            {
                "name": "Pizza Place",
                "rating": 3,
                "id": pizza_place.id,
                "history_type": "+",
            },
        )
        self.assertRecordValues(
            update_record,
            Restaurant,
            {
                "name": "Pizza Place",
                "rating": 6,
                "id": pizza_place.id,
                "history_type": "~",
            },
        )

    @override_settings(SIMPLE_HISTORY_ENABLED=False)
    def test_save_with_disabled_history(self):
        anthony = Person.objects.create(name="Anthony Gillard")
        anthony.name = "something else"
        anthony.save()
        self.assertEqual(Person.history.count(), 0)
        anthony.delete()
        self.assertEqual(Person.history.count(), 0)

    def test_save_without_historical_record_for_registered_model(self):
        model = ExternalModelSpecifiedWithAppParam.objects.create(
            name="registered model"
        )
        self.assertTrue(hasattr(model, "save_without_historical_record"))

    def test_save_raises_exception(self):
        anthony = Person(name="Anthony Gillard")
        with self.assertRaises(RuntimeError):
            anthony.save_without_historical_record()
        self.assertFalse(hasattr(anthony, "skip_history_when_saving"))
        self.assertEqual(Person.history.count(), 0)
        anthony.save()
        self.assertEqual(Person.history.count(), 1)

    def test_foreignkey_field(self):
        why_poll = Poll.objects.create(question="why?", pub_date=today)
        how_poll = Poll.objects.create(question="how?", pub_date=today)
        choice = Choice.objects.create(poll=why_poll, votes=0)
        choice.poll = how_poll
        choice.save()
        update_record, create_record = Choice.history.all()
        self.assertRecordValues(
            create_record,
            Choice,
            {"poll_id": why_poll.id, "votes": 0, "id": choice.id, "history_type": "+"},
        )
        self.assertRecordValues(
            update_record,
            Choice,
            {"poll_id": how_poll.id, "votes": 0, "id": choice.id, "history_type": "~"},
        )

    def test_foreignkey_still_allows_reverse_lookup_via_set_attribute(self):
        lib = Library.objects.create()
        state = State.objects.create(library=lib)
        self.assertTrue(hasattr(lib, "state_set"))
        self.assertIsNone(
            state._meta.get_field("library").remote_field.related_name,
            "the '+' shouldn't leak through to the original "
            "model's field related_name",
        )

    def test_file_field(self):
        filename = str(uuid.uuid4())
        model = FileModel.objects.create(file=get_fake_file(filename))
        self.assertEqual(model.file.name, f"files/{filename}")
        model.file.delete()
        update_record, create_record = model.history.all()
        self.assertEqual(create_record.file, f"files/{filename}")
        self.assertEqual(update_record.file, "")

    def test_file_field_with_char_field_setting(self):
        # setting means history table's file field is a CharField
        file_field = CharFieldFileModel.history.model._meta.get_field("file")
        self.assertIs(type(file_field), models.CharField)
        self.assertEqual(file_field.max_length, 100)
        # file field works the same as test_file_field()
        filename = str(uuid.uuid4())
        model = CharFieldFileModel.objects.create(file=get_fake_file(filename))
        self.assertEqual(model.file.name, f"files/{filename}")
        model.file.delete()
        update_record, create_record = model.history.all()
        self.assertEqual(create_record.file, f"files/{filename}")
        self.assertEqual(update_record.file, "")

    def test_inheritance(self):
        pizza_place = Restaurant.objects.create(name="Pizza Place", rating=3)
        pizza_place.rating = 4
        pizza_place.save()
        update_record, create_record = Restaurant.updates.all()
        self.assertRecordValues(
            create_record,
            Restaurant,
            {
                "name": "Pizza Place",
                "rating": 3,
                "id": pizza_place.id,
                "history_type": "+",
            },
        )
        self.assertRecordValues(
            update_record,
            Restaurant,
            {
                "name": "Pizza Place",
                "rating": 4,
                "id": pizza_place.id,
                "history_type": "~",
            },
        )

    def test_reverse_historical(self):
        """Tests how we can go from instance to historical record."""
        document = Document.objects.create()
        historic = document.history.all()[0]
        instance = historic.instance
        self.assertEqual(
            getattr(instance, SIMPLE_HISTORY_REVERSE_ATTR_NAME).history_date,
            historic.history_date,
        )

    def test_specify_history_user(self):
        user1 = User.objects.create_user("user1", "1@example.com")
        user2 = User.objects.create_user("user2", "1@example.com")
        document = Document.objects.create(changed_by=user1)
        document.changed_by = user2
        document.save()
        document.changed_by = None
        document.save()
        self.assertEqual(
            [d.history_user for d in document.history.all()], [None, user2, user1]
        )

    def test_specify_history_user_self_reference_delete(self):
        user1 = User.objects.create_user("user1", "1@example.com")
        user2 = User.objects.create_user("user2", "1@example.com")
        document = Document.objects.create(changed_by=user1)
        document.changed_by = user2
        document.save()
        document.changed_by = None
        document.save()
        self.assertEqual(
            [d.history_user for d in document.history.all()], [None, user2, user1]
        )

        # Change back to user1
        document.changed_by = user1
        document.save()

        # Deleting user1 will cascade delete the document,
        # but fails when it tries to make the historical
        # record for the deleted user1.
        # This test performs differently on Postgres vs. SQLite
        # because of how the two database handle database constraints
        try:
            user1.delete()
        except IntegrityError as e:
            self.fail(e)

    def test_specify_history_date_1(self):
        temperature = Temperature.objects.create(
            location="London", temperature=14, _history_date=today
        )
        temperature.temperature = 16
        temperature._history_date = yesterday
        temperature.save()
        self.assertEqual(
            [t.history_date for t in temperature.history.all()], [today, yesterday]
        )

    def test_specify_history_date_2(self):
        river = WaterLevel.objects.create(waters="Thames", level=2.5, date=today)
        river.level = 2.6
        river.date = yesterday
        river.save()
        for t in river.history.all():
            self.assertEqual(t.date, t.history_date)

    def test_non_default_primary_key_save(self):
        book1 = Book.objects.create(isbn="1-84356-028-1")
        book2 = Book.objects.create(isbn="1-84356-028-2")
        library = Library.objects.create(book=book1)
        library.book = book2
        library.save()
        library.book = None
        library.save()
        self.assertEqual(
            [lib.book_id for lib in library.history.all()], [None, book2.pk, book1.pk]
        )

    def test_string_defined_foreign_key_save(self):
        library1 = Library.objects.create()
        library2 = Library.objects.create()
        state = State.objects.create(library=library1)
        state.library = library2
        state.save()
        state.library = None
        state.save()
        self.assertEqual(
            [s.library_id for s in state.history.all()],
            [None, library2.pk, library1.pk],
        )

    def test_self_referential_foreign_key(self):
        model = SelfFK.objects.create()
        other = SelfFK.objects.create()
        model.fk = model
        model.save()
        model.fk = other
        model.save()
        self.assertEqual(
            [m.fk_id for m in model.history.all()], [other.id, model.id, None]
        )

    def test_to_field_foreign_key_save(self):
        country = Country.objects.create(code="US")
        country2 = Country.objects.create(code="CA")
        province = Province.objects.create(country=country)
        province.country = country2
        province.save()
        self.assertEqual(
            [c.country_id for c in province.history.all()],
            [country2.code, country.code],
        )

    def test_db_column_foreign_key_save(self):
        country = Country.objects.create(code="US")
        city = City.objects.create(country=country)
        country_field = City._meta.get_field("country")
        self.assertIn(
            getattr(country_field, "db_column"), str(city.history.all().query)
        )

    def test_raw_save(self):
        document = Document()
        document.save_base(raw=True)
        self.assertEqual(document.history.count(), 0)
        document.save()
        self.assertRecordValues(
            document.history.get(),
            Document,
            {"changed_by_id": None, "id": document.id, "history_type": "~"},
        )

    def test_unicode_verbose_name(self):
        instance = UnicodeVerboseName()
        instance.save()
        self.assertEqual(
            "historical \u570b", instance.history.all()[0]._meta.verbose_name
        )

    def test_user_can_set_verbose_name(self):
        b = Book(isbn="54321")
        b.save()
        self.assertEqual("dead trees", b.history.all()[0]._meta.verbose_name)

    def test_historical_verbose_name_follows_model_verbose_name(self):
        library = Library()
        library.save()
        self.assertEqual(
            "historical quiet please", library.history.get()._meta.verbose_name
        )

    def test_unicode_verbose_name_plural(self):
        instance = UnicodeVerboseNamePlural()
        instance.save()
        self.assertEqual(
            "historical \u570b", instance.history.all()[0]._meta.verbose_name_plural
        )

    def test_user_can_set_verbose_name_plural(self):
        b = Book(isbn="54321")
        b.save()
        self.assertEqual(
            "dead trees plural", b.history.all()[0]._meta.verbose_name_plural
        )

    def test_historical_verbose_name_plural_follows_model_verbose_name_plural(self):
        library = Library()
        library.save()
        self.assertEqual(
            "historical quiet please plural",
            library.history.get()._meta.verbose_name_plural,
        )

    def test_foreignkey_primarykey(self):
        """Test saving a tracked model with a `ForeignKey` primary key."""
        poll = Poll(pub_date=today)
        poll.save()
        poll_info = PollInfo(poll=poll)
        poll_info.save()

    def test_model_with_excluded_fields(self):
        p = PollWithExcludeFields(
            question="what's up?", pub_date=today, place="The Pub"
        )
        p.save()
        history = PollWithExcludeFields.history.all()[0]
        all_fields_names = [f.name for f in history._meta.fields]
        self.assertIn("question", all_fields_names)
        self.assertNotIn("pub_date", all_fields_names)
        self.assertEqual(history.question, p.question)
        self.assertEqual(history.place, p.place)

        most_recent = p.history.most_recent()
        self.assertIn("question", all_fields_names)
        self.assertNotIn("pub_date", all_fields_names)
        self.assertEqual(most_recent.__class__, PollWithExcludeFields)
        self.assertIn("pub_date", history._history_excluded_fields)
        self.assertEqual(most_recent.question, p.question)
        self.assertEqual(most_recent.place, p.place)

    def test_user_model_override(self):
        user1 = User.objects.create_user("user1", "1@example.com")
        user2 = User.objects.create_user("user2", "1@example.com")
        member1 = BucketMember.objects.create(name="member1", user=user1)
        member2 = BucketMember.objects.create(name="member2", user=user2)
        bucket_data = BucketData.objects.create(changed_by=member1)
        bucket_data.changed_by = member2
        bucket_data.save()
        bucket_data.changed_by = None
        bucket_data.save()
        self.assertEqual(
            [d.history_user for d in bucket_data.history.all()],
            [None, member2, member1],
        )

    def test_user_model_override_registered(self):
        user1 = User.objects.create_user("user1", "1@example.com")
        user2 = User.objects.create_user("user2", "1@example.com")
        member1 = BucketMember.objects.create(name="member1", user=user1)
        member2 = BucketMember.objects.create(name="member2", user=user2)
        bucket_data = BucketDataRegisterChangedBy.objects.create(changed_by=member1)
        bucket_data.changed_by = member2
        bucket_data.save()
        bucket_data.changed_by = None
        bucket_data.save()
        self.assertEqual(
            [d.history_user for d in bucket_data.history.all()],
            [None, member2, member1],
        )

    def test_uuid_history_id(self):
        entry = UUIDModel.objects.create()

        history = entry.history.all()[0]
        self.assertTrue(isinstance(history.history_id, uuid.UUID))

    def test_uuid_default_history_id(self):
        entry = UUIDDefaultModel.objects.create()

        history = entry.history.all()[0]
        self.assertTrue(isinstance(history.history_id, uuid.UUID))

    def test_default_history_change_reason(self):
        entry = CharFieldChangeReasonModel.objects.create(greeting="what's up?")
        history = entry.history.get()

        self.assertEqual(history.history_change_reason, None)

    def test_charfield_history_change_reason(self):
        # Default CharField and length
        entry = CharFieldChangeReasonModel.objects.create(greeting="what's up?")
        entry.greeting = "what is happening?"
        entry.save()
        update_change_reason(entry, "Change greeting.")

        history = entry.history.all()[0]
        field = history._meta.get_field("history_change_reason")

        self.assertTrue(isinstance(field, models.CharField))
        self.assertTrue(field.max_length, 100)

    def test_default_textfield_history_change_reason(self):
        # TextField usage is determined by settings
        entry = DefaultTextFieldChangeReasonModel.objects.create(greeting="what's up?")
        entry.greeting = "what is happening?"
        entry.save()

        reason = "Change greeting"
        update_change_reason(entry, reason)

        history = entry.history.all()[0]
        field = history._meta.get_field("history_change_reason")

        self.assertTrue(isinstance(field, models.TextField))
        self.assertEqual(history.history_change_reason, reason)

    def test_user_textfield_history_change_reason(self):
        # TextField instance is passed in init
        entry = UserTextFieldChangeReasonModel.objects.create(greeting="what's up?")
        entry.greeting = "what is happening?"
        entry.save()

        reason = "Change greeting"
        update_change_reason(entry, reason)

        history = entry.history.all()[0]
        field = history._meta.get_field("history_change_reason")

        self.assertTrue(isinstance(field, models.TextField))
        self.assertEqual(history.history_change_reason, reason)

    def test_history_diff_includes_changed_fields(self):
        p = Poll.objects.create(question="what's up?", pub_date=today)
        p.question = "what's up, man?"
        p.save()
        new_record, old_record = p.history.all()
        with self.assertNumQueries(0):
            delta = new_record.diff_against(old_record)
        expected_change = ModelChange("question", "what's up?", "what's up, man")
        self.assertEqual(delta.changed_fields, ["question"])
        self.assertEqual(delta.old_record, old_record)
        self.assertEqual(delta.new_record, new_record)
        self.assertEqual(expected_change.field, delta.changes[0].field)

    def test_history_diff_does_not_include_unchanged_fields(self):
        p = Poll.objects.create(question="what's up?", pub_date=today)
        p.question = "what's up, man?"
        p.save()
        new_record, old_record = p.history.all()
        with self.assertNumQueries(0):
            delta = new_record.diff_against(old_record)
        self.assertNotIn("pub_date", delta.changed_fields)

    def test_history_diff_includes_changed_fields_of_base_model(self):
        r = InheritedRestaurant.objects.create(name="McDonna", serves_hot_dogs=False)
        # change base model field
        r.name = "DonnutsKing"
        r.save()
        new_record, old_record = r.history.all()
        with self.assertNumQueries(0):
            delta = new_record.diff_against(old_record)
        expected_change = ModelChange("name", "McDonna", "DonnutsKing")
        self.assertEqual(delta.changed_fields, ["name"])
        self.assertEqual(delta.old_record, old_record)
        self.assertEqual(delta.new_record, new_record)
        self.assertEqual(expected_change.field, delta.changes[0].field)

    def test_history_diff_with_incorrect_type(self):
        p = Poll.objects.create(question="what's up?", pub_date=today)
        p.question = "what's up, man?"
        p.save()
        new_record, old_record = p.history.all()
        with self.assertRaises(TypeError):
            new_record.diff_against("something")

    def test_history_diff_with_excluded_fields(self):
        p = Poll.objects.create(question="what's up?", pub_date=today)
        p.question = "what's up, man?"
        p.save()
        new_record, old_record = p.history.all()
        with self.assertNumQueries(0):
            delta = new_record.diff_against(old_record, excluded_fields=("question",))
        self.assertEqual(delta.changed_fields, [])
        self.assertEqual(delta.changes, [])

    def test_history_diff_with_included_fields(self):
        p = Poll.objects.create(question="what's up?", pub_date=today)
        p.question = "what's up, man?"
        p.save()
        new_record, old_record = p.history.all()
        with self.assertNumQueries(0):
            delta = new_record.diff_against(old_record, included_fields=[])
        self.assertEqual(delta.changed_fields, [])
        self.assertEqual(delta.changes, [])

        with self.assertNumQueries(0):
            delta = new_record.diff_against(old_record, included_fields=["question"])
        self.assertEqual(delta.changed_fields, ["question"])
        self.assertEqual(len(delta.changes), 1)

    def test_history_diff_with_non_editable_field(self):
        p = PollWithNonEditableField.objects.create(
            question="what's up?", pub_date=today
        )
        p.question = "what's up, man?"
        p.save()
        new_record, old_record = p.history.all()
        with self.assertNumQueries(0):
            delta = new_record.diff_against(old_record)
        self.assertEqual(delta.changed_fields, ["question"])
        self.assertEqual(len(delta.changes), 1)

    def test_history_with_unknown_field(self):
        p = Poll.objects.create(question="what's up?", pub_date=today)
        p.question = "what's up, man?"
        p.save()
        new_record, old_record = p.history.all()
        with self.assertRaises(KeyError):
            with self.assertNumQueries(0):
                new_record.diff_against(old_record, included_fields=["unknown_field"])

        with self.assertNumQueries(0):
            new_record.diff_against(old_record, excluded_fields=["unknown_field"])


class GetPrevRecordAndNextRecordTestCase(TestCase):
    def assertRecordsMatch(self, record_a, record_b):
        self.assertEqual(record_a, record_b)
        self.assertEqual(record_a.question, record_b.question)

    def setUp(self):
        self.poll = Poll(question="what's up?", pub_date=today)
        self.poll.save()

    def test_get_prev_record(self):
        self.poll.question = "ask questions?"
        self.poll.save()
        self.poll.question = "eh?"
        self.poll.save()
        self.poll.question = "one more?"
        self.poll.save()
        first_record = self.poll.history.filter(question="what's up?").get()
        second_record = self.poll.history.filter(question="ask questions?").get()
        third_record = self.poll.history.filter(question="eh?").get()
        fourth_record = self.poll.history.filter(question="one more?").get()

        with self.assertNumQueries(1):
            self.assertRecordsMatch(second_record.prev_record, first_record)
        with self.assertNumQueries(1):
            self.assertRecordsMatch(third_record.prev_record, second_record)
        with self.assertNumQueries(1):
            self.assertRecordsMatch(fourth_record.prev_record, third_record)

    def test_get_prev_record_none_if_only(self):
        self.assertEqual(self.poll.history.count(), 1)
        record = self.poll.history.get()
        self.assertIsNone(record.prev_record)

    def test_get_prev_record_none_if_earliest(self):
        self.poll.question = "ask questions?"
        self.poll.save()
        first_record = self.poll.history.filter(question="what's up?").get()
        self.assertIsNone(first_record.prev_record)

    def test_get_prev_record_with_custom_manager_name(self):
        instance = CustomManagerNameModel.objects.create(name="Test name 1")
        instance.name = "Test name 2"
        instance.save()
        first_record = instance.log.filter(name="Test name 1").get()
        second_record = instance.log.filter(name="Test name 2").get()

        self.assertEqual(second_record.prev_record, first_record)

    def test_get_prev_record_with_excluded_field(self):
        instance = PollWithExcludeFields.objects.create(
            question="what's up?", pub_date=today
        )
        instance.question = "ask questions?"
        instance.save()
        first_record = instance.history.filter(question="what's up?").get()
        second_record = instance.history.filter(question="ask questions?").get()

        with self.assertNumQueries(1):
            self.assertRecordsMatch(second_record.prev_record, first_record)

    def test_get_next_record(self):
        self.poll.question = "ask questions?"
        self.poll.save()
        self.poll.question = "eh?"
        self.poll.save()
        self.poll.question = "one more?"
        self.poll.save()
        first_record = self.poll.history.filter(question="what's up?").get()
        second_record = self.poll.history.filter(question="ask questions?").get()
        third_record = self.poll.history.filter(question="eh?").get()
        fourth_record = self.poll.history.filter(question="one more?").get()
        self.assertIsNone(fourth_record.next_record)

        with self.assertNumQueries(1):
            self.assertRecordsMatch(first_record.next_record, second_record)
        with self.assertNumQueries(1):
            self.assertRecordsMatch(second_record.next_record, third_record)
        with self.assertNumQueries(1):
            self.assertRecordsMatch(third_record.next_record, fourth_record)

    def test_get_next_record_none_if_only(self):
        self.assertEqual(self.poll.history.count(), 1)
        record = self.poll.history.get()
        self.assertIsNone(record.next_record)

    def test_get_next_record_none_if_most_recent(self):
        self.poll.question = "ask questions?"
        self.poll.save()
        recent_record = self.poll.history.filter(question="ask questions?").get()
        self.assertIsNone(recent_record.next_record)

    def test_get_next_record_with_custom_manager_name(self):
        instance = CustomManagerNameModel.objects.create(name="Test name 1")
        instance.name = "Test name 2"
        instance.save()
        first_record = instance.log.filter(name="Test name 1").get()
        second_record = instance.log.filter(name="Test name 2").get()

        self.assertEqual(first_record.next_record, second_record)

    def test_get_next_record_with_excluded_field(self):
        instance = PollWithExcludeFields.objects.create(
            question="what's up?", pub_date=today
        )
        instance.question = "ask questions?"
        instance.save()
        first_record = instance.history.filter(question="what's up?").get()
        second_record = instance.history.filter(question="ask questions?").get()

        with self.assertNumQueries(1):
            self.assertRecordsMatch(first_record.next_record, second_record)


class CreateHistoryModelTests(unittest.TestCase):
    def test_create_history_model_with_one_to_one_field_to_integer_field(self):
        records = HistoricalRecords()
        records.module = AdminProfile.__module__
        try:
            records.create_history_model(AdminProfile, False)
        except Exception:
            self.fail(
                "SimpleHistory should handle foreign keys to one to one"
                "fields to integer fields without throwing an exception"
            )

    def test_create_history_model_with_one_to_one_field_to_char_field(self):
        records = HistoricalRecords()
        records.module = Bookcase.__module__
        try:
            records.create_history_model(Bookcase, False)
        except Exception:
            self.fail(
                "SimpleHistory should handle foreign keys to one to one"
                "fields to char fields without throwing an exception."
            )

    def test_create_history_model_with_multiple_one_to_ones(self):
        records = HistoricalRecords()
        records.module = MultiOneToOne.__module__
        try:
            records.create_history_model(MultiOneToOne, False)
        except Exception:
            self.fail(
                "SimpleHistory should handle foreign keys to one to one"
                "fields to one to one fields without throwing an "
                "exception."
            )


class CustomModelNameTests(unittest.TestCase):
    def verify_custom_model_name_feature(
        self, model, expected_class_name, expected_table_name
    ):
        history_model = model.history.model
        self.assertEqual(history_model.__name__, expected_class_name)
        self.assertEqual(history_model._meta.db_table, expected_table_name)

    def test_instantiate_history_model_with_custom_model_name_as_string(self):
        try:
            from ..models import OverrideModelNameAsString
        except ImportError:
            self.fail("{}OverrideModelNameAsString is in wrong module")
        expected_cls_name = "MyHistoricalCustomNameModel"
        self.verify_custom_model_name_feature(
            OverrideModelNameAsString(),
            expected_cls_name,
            f"tests_{expected_cls_name.lower()}",
        )

    def test_register_history_model_with_custom_model_name_override(self):
        try:
            from ..models import OverrideModelNameRegisterMethod1
        except ImportError:
            self.fail("OverrideModelNameRegisterMethod1 is in wrong module")

        cls = OverrideModelNameRegisterMethod1()
        expected_cls_name = "MyOverrideModelNameRegisterMethod1"
        self.verify_custom_model_name_feature(
            cls, expected_cls_name, f"tests_{expected_cls_name.lower()}"
        )

        from simple_history import register

        from ..models import OverrideModelNameRegisterMethod2

        try:
            register(
                OverrideModelNameRegisterMethod2,
                custom_model_name=lambda x: f"{x}",
            )
        except ValueError:
            self.assertRaises(ValueError)

    def test_register_history_model_with_custom_model_name_from_abstract_model(self):
        cls = OverrideModelNameUsingBaseModel1
        expected_cls_name = f"Audit{cls.__name__}"
        self.verify_custom_model_name_feature(
            cls, expected_cls_name, "tests_" + expected_cls_name.lower()
        )

    def test_register_history_model_with_custom_model_name_from_external_model(self):
        from ..models import OverrideModelNameUsingExternalModel1

        cls = OverrideModelNameUsingExternalModel1
        expected_cls_name = f"Audit{cls.__name__}"
        self.verify_custom_model_name_feature(
            cls, expected_cls_name, "tests_" + expected_cls_name.lower()
        )

        from ..models import OverrideModelNameUsingExternalModel2

        cls = OverrideModelNameUsingExternalModel2
        expected_cls_name = f"Audit{cls.__name__}"
        self.verify_custom_model_name_feature(
            cls, expected_cls_name, "external_" + expected_cls_name.lower()
        )


class AppLabelTest(TestCase):
    def get_table_name(self, manager):
        return manager.model._meta.db_table

    def test_explicit_app_label(self):
        self.assertEqual(
            self.get_table_name(ExternalModelWithAppLabel.objects),
            "external_externalmodelwithapplabel",
        )

        self.assertEqual(
            self.get_table_name(ExternalModelWithAppLabel.history),
            "external_historicalexternalmodelwithapplabel",
        )

    def test_default_app_label(self):
        self.assertEqual(
            self.get_table_name(ExternalModel.objects), "external_externalmodel"
        )
        self.assertEqual(
            self.get_table_name(ExternalModel.history),
            "external_historicalexternalmodel",
        )

    def test_register_app_label(self):
        self.assertEqual(
            self.get_table_name(ExternalModelSpecifiedWithAppParam.objects),
            "tests_externalmodelspecifiedwithappparam",
        )
        self.assertEqual(
            self.get_table_name(ExternalModelSpecifiedWithAppParam.histories),
            "external_historicalexternalmodelspecifiedwithappparam",
        )
        self.assertEqual(
            self.get_table_name(ExternalModelRegistered.objects),
            "external_externalmodelregistered",
        )
        self.assertEqual(
            self.get_table_name(ExternalModelRegistered.histories),
            "tests_historicalexternalmodelregistered",
        )
        self.assertEqual(
            self.get_table_name(ConcreteExternal.objects), "tests_concreteexternal"
        )
        self.assertEqual(
            self.get_table_name(ConcreteExternal.history),
            "tests_historicalconcreteexternal",
        )

    def test_get_model(self):
        self.assertEqual(
            get_model("external", "ExternalModelWithAppLabel"),
            ExternalModelWithAppLabel,
        )
        self.assertEqual(
            get_model("external", "HistoricalExternalModelWithAppLabel"),
            ExternalModelWithAppLabel.history.model,
        )

        self.assertEqual(get_model("external", "ExternalModel"), ExternalModel)
        self.assertEqual(
            get_model("external", "HistoricalExternalModel"),
            ExternalModel.history.model,
        )

        self.assertEqual(
            get_model("tests", "ExternalModelSpecifiedWithAppParam"),
            ExternalModelSpecifiedWithAppParam,
        )
        self.assertEqual(
            get_model("external", "HistoricalExternalModelSpecifiedWithAppParam"),
            ExternalModelSpecifiedWithAppParam.histories.model,
        )

        self.assertEqual(
            get_model("external", "ExternalModelRegistered"), ExternalModelRegistered
        )
        self.assertEqual(
            get_model("tests", "HistoricalExternalModelRegistered"),
            ExternalModelRegistered.histories.model,
        )

        # Test that historical model is defined within app of concrete
        # model rather than abstract base model
        self.assertEqual(get_model("tests", "ConcreteExternal"), ConcreteExternal)
        self.assertEqual(
            get_model("tests", "HistoricalConcreteExternal"),
            ConcreteExternal.history.model,
        )


class HistoryManagerTest(TestCase):
    def test_most_recent(self):
        poll = Poll.objects.create(question="what's up?", pub_date=today)
        poll.question = "how's it going?"
        poll.save()
        poll.question = "why?"
        poll.save()
        poll.question = "how?"
        most_recent = poll.history.most_recent()
        self.assertEqual(most_recent.__class__, Poll)
        self.assertEqual(most_recent.question, "why?")

    def test_get_model(self):
        self.assertEqual(get_model("tests", "poll"), Poll)
        self.assertEqual(get_model("tests", "historicalpoll"), HistoricalPoll)

    def test_most_recent_on_model_class(self):
        Poll.objects.create(question="what's up?", pub_date=today)
        self.assertRaises(TypeError, Poll.history.most_recent)

    def test_most_recent_nonexistant(self):
        # Unsaved poll
        poll = Poll(question="what's up?", pub_date=today)
        self.assertRaises(Poll.DoesNotExist, poll.history.most_recent)
        # Deleted poll
        poll.save()
        poll.delete()
        self.assertRaises(Poll.DoesNotExist, poll.history.most_recent)

    def test_date_indexing_options(self):
        records = HistoricalRecords()
        delattr(settings, "SIMPLE_HISTORY_DATE_INDEX")
        self.assertTrue(records._date_indexing)
        settings.SIMPLE_HISTORY_DATE_INDEX = False
        self.assertFalse(records._date_indexing)
        settings.SIMPLE_HISTORY_DATE_INDEX = "Composite"
        self.assertEqual(records._date_indexing, "composite")
        settings.SIMPLE_HISTORY_DATE_INDEX = "foo"
        with self.assertRaises(ImproperlyConfigured):
            records._date_indexing
        settings.SIMPLE_HISTORY_DATE_INDEX = 42
        with self.assertRaises(ImproperlyConfigured):
            records._date_indexing
        settings.SIMPLE_HISTORY_DATE_INDEX = None
        with self.assertRaises(ImproperlyConfigured):
            records._date_indexing
        delattr(settings, "SIMPLE_HISTORY_DATE_INDEX")

    def test_as_of(self):
        poll = Poll.objects.create(question="what's up?", pub_date=today)
        poll.question = "how's it going?"
        poll.save()
        poll.question = "why?"
        poll.save()
        poll.question = "how?"
        most_recent = poll.history.most_recent()
        self.assertEqual(most_recent.question, "why?")
        times = [r.history_date for r in poll.history.all()]

        def question_as_of(time):
            return poll.history.as_of(time).question

        self.assertEqual(question_as_of(times[0]), "why?")
        self.assertEqual(question_as_of(times[1]), "how's it going?")
        self.assertEqual(question_as_of(times[2]), "what's up?")

    def test_as_of_nonexistant(self):
        # Unsaved poll
        poll = Poll(question="what's up?", pub_date=today)
        time = datetime.now()
        self.assertRaises(Poll.DoesNotExist, poll.history.as_of, time)
        # Deleted poll
        poll.save()
        poll.delete()
        self.assertRaises(Poll.DoesNotExist, poll.history.as_of, time)

    def test_as_of_excluded_many_to_many_succeeds(self):
        other1 = ManyToManyModelOther.objects.create(name="test1")
        other2 = ManyToManyModelOther.objects.create(name="test2")

        m = ModelWithExcludedManyToMany.objects.create(name="test")
        m.other.add(other1, other2)

        # This will fail if the ManyToMany field is not excluded.
        self.assertEqual(m.history.as_of(datetime.now()), m)

    def test_foreignkey_field(self):
        why_poll = Poll.objects.create(question="why?", pub_date=today)
        how_poll = Poll.objects.create(question="how?", pub_date=today)
        choice = Choice.objects.create(poll=why_poll, votes=0)
        choice.poll = how_poll
        choice.save()
        most_recent = choice.history.most_recent()
        self.assertEqual(most_recent.poll.pk, how_poll.pk)
        times = [r.history_date for r in choice.history.all()]

        def poll_as_of(time):
            return choice.history.as_of(time).poll

        self.assertEqual(poll_as_of(times[0]).pk, how_poll.pk)
        self.assertEqual(poll_as_of(times[1]).pk, why_poll.pk)

    def test_abstract_inheritance(self):
        for klass in (ConcreteAttr, ConcreteUtil):
            obj = klass.objects.create()
            obj.save()
            update_record, create_record = klass.history.all()
            self.assertTrue(isinstance(update_record, AbstractBase))
            self.assertTrue(isinstance(create_record, AbstractBase))

    def test_invalid_bases(self):
        invalid_bases = (AbstractBase, "InvalidBases")
        for bases in invalid_bases:
            self.assertRaises(TypeError, HistoricalRecords, bases=bases)

    def test_import_related(self):
        field_object = HistoricalChoice._meta.get_field("poll")
        related_model = field_object.remote_field.related_model
        self.assertEqual(related_model, HistoricalChoice)

    def test_string_related(self):
        field_object = HistoricalState._meta.get_field("library")
        related_model = field_object.remote_field.related_model
        self.assertEqual(related_model, HistoricalState)

    def test_state_serialization_of_customfk(self):
        from django.db.migrations import state

        state.ModelState.from_model(HistoricalCustomFKError)


class TestOrderWrtField(TestCase):
    """Check behaviour of _order field added by Meta.order_with_respect_to.

    The Meta.order_with_respect_to option adds an OrderWrt field named
    "_order", where OrderWrt is a proxy class for an IntegerField that sets
    some default options.

    The simple_history strategy is:
    - Convert to a plain IntegerField in the historical record
    - When restoring a historical instance, add the old value.  This may
      result in duplicate ordering values and non-deterministic ordering.
    """

    def setUp(self):
        """Create works in published order."""
        s = self.series = Series.objects.create(
            name="The Chronicles of Narnia", author="C.S. Lewis"
        )
        self.w_lion = s.works.create(title="The Lion, the Witch and the Wardrobe")
        self.w_caspian = s.works.create(title="Prince Caspian")
        self.w_voyage = s.works.create(title="The Voyage of the Dawn Treader")
        self.w_chair = s.works.create(title="The Silver Chair")
        self.w_horse = s.works.create(title="The Horse and His Boy")
        self.w_nephew = s.works.create(title="The Magician's Nephew")
        self.w_battle = s.works.create(title="The Last Battle")

    def test_order(self):
        """Confirm that works are ordered by creation."""
        order = self.series.get_serieswork_order()
        expected = [
            self.w_lion.pk,
            self.w_caspian.pk,
            self.w_voyage.pk,
            self.w_chair.pk,
            self.w_horse.pk,
            self.w_nephew.pk,
            self.w_battle.pk,
        ]
        self.assertSequenceEqual(order, expected)
        self.assertEqual(0, self.w_lion._order)
        self.assertEqual(1, self.w_caspian._order)
        self.assertEqual(2, self.w_voyage._order)
        self.assertEqual(3, self.w_chair._order)
        self.assertEqual(4, self.w_horse._order)
        self.assertEqual(5, self.w_nephew._order)
        self.assertEqual(6, self.w_battle._order)

    def test_order_field_in_historical_model(self):
        work_order_field = self.w_lion._meta.get_field("_order")
        self.assertEqual(type(work_order_field), OrderWrt)

        history = self.w_lion.history.all()[0]
        history_order_field = history._meta.get_field("_order")
        self.assertEqual(type(history_order_field), models.IntegerField)

    def test_history_object_has_order(self):
        history = self.w_lion.history.all()[0]
        self.assertEqual(self.w_lion._order, history.history_object._order)

    def test_restore_object_with_changed_order(self):
        # Change a title
        self.w_caspian.title = "Prince Caspian: The Return to Narnia"
        self.w_caspian.save()
        self.assertEqual(2, len(self.w_caspian.history.all()))
        self.assertEqual(1, self.w_caspian._order)

        # Switch to internal chronological order
        chronological = [
            self.w_nephew.pk,
            self.w_lion.pk,
            self.w_horse.pk,
            self.w_caspian.pk,
            self.w_voyage.pk,
            self.w_chair.pk,
            self.w_battle.pk,
        ]
        self.series.set_serieswork_order(chronological)
        self.assertSequenceEqual(self.series.get_serieswork_order(), chronological)

        # This uses an update, not a save, so no new history is created
        w_caspian = SeriesWork.objects.get(id=self.w_caspian.id)
        self.assertEqual(2, len(w_caspian.history.all()))
        self.assertEqual(1, w_caspian.history.all()[0]._order)
        self.assertEqual(1, w_caspian.history.all()[1]._order)
        self.assertEqual(3, w_caspian._order)

        # Revert to first title, old order
        old = w_caspian.history.all()[1].history_object
        old.save()
        w_caspian = SeriesWork.objects.get(id=self.w_caspian.id)
        self.assertEqual(3, len(w_caspian.history.all()))
        self.assertEqual(1, w_caspian.history.all()[0]._order)
        self.assertEqual(1, w_caspian.history.all()[1]._order)
        self.assertEqual(1, w_caspian.history.all()[2]._order)
        self.assertEqual(1, w_caspian._order)  # The order changed
        w_lion = SeriesWork.objects.get(id=self.w_lion.id)
        self.assertEqual(1, w_lion._order)  # and is identical to another order

        # New order is non-deterministic around identical IDs
        series = Series.objects.get(id=self.series.id)
        order = series.get_serieswork_order()
        self.assertEqual(order[0], self.w_nephew.pk)
        self.assertTrue(order[1] in (self.w_lion.pk, self.w_caspian.pk))
        self.assertTrue(order[2] in (self.w_lion.pk, self.w_caspian.pk))
        self.assertEqual(order[3], self.w_horse.pk)
        self.assertEqual(order[4], self.w_voyage.pk)
        self.assertEqual(order[5], self.w_chair.pk)
        self.assertEqual(order[6], self.w_battle.pk)

    def test_migrations_include_order(self):
        from django.db.migrations import state

        model_state = state.ModelState.from_model(SeriesWork.history.model)
        found = False
        # `fields` is a dict in Django 3.1
        fields = None
        if isinstance(model_state.fields, dict):
            fields = model_state.fields.items()
        else:
            fields = model_state.fields

        for name, field in fields:
            if name == "_order":
                found = True
                self.assertEqual(type(field), models.IntegerField)
        self.assertTrue(found, "_order not in fields " + repr(model_state.fields))


class TestLatest(TestCase):
    """Test behavior of `latest()` without any field parameters"""

    def setUp(self):
        poll = Poll.objects.create(question="Does `latest()` work?", pub_date=yesterday)
        poll.pub_date = today
        poll.save()

    def write_history(self, new_attributes):
        poll_history = HistoricalPoll.objects.all()
        for historical_poll, new_values in zip(poll_history, new_attributes):
            for fieldname, value in new_values.items():
                setattr(historical_poll, fieldname, value)
            historical_poll.save()

    def test_ordered(self):
        self.write_history(
            [{"pk": 1, "history_date": yesterday}, {"pk": 2, "history_date": today}]
        )
        self.assertEqual(HistoricalPoll.objects.latest().pk, 2)

    def test_jumbled(self):
        self.write_history(
            [{"pk": 1, "history_date": today}, {"pk": 2, "history_date": yesterday}]
        )
        self.assertEqual(HistoricalPoll.objects.latest().pk, 1)

    def test_sameinstant(self):
        self.write_history(
            [{"pk": 1, "history_date": yesterday}, {"pk": 2, "history_date": yesterday}]
        )
        self.assertEqual(HistoricalPoll.objects.latest().pk, 2)


class TestMissingOneToOne(TestCase):
    def setUp(self):
        self.manager1 = Employee.objects.create()
        self.manager2 = Employee.objects.create()
        self.employee = Employee.objects.create(manager=self.manager1)
        self.employee.manager = self.manager2
        self.employee.save()
        self.manager1_id = self.manager1.id
        self.manager1.delete()

    def test_history_is_complete(self):
        historical_manager_ids = list(
            self.employee.history.order_by("pk").values_list("manager_id", flat=True)
        )
        self.assertEqual(historical_manager_ids, [self.manager1_id, self.manager2.id])

    def test_restore_employee(self):
        historical = self.employee.history.order_by("pk")[0]
        original = historical.instance
        self.assertEqual(original.manager_id, self.manager1_id)
        with self.assertRaises(Employee.DoesNotExist):
            original.manager


class CustomTableNameTest1(TestCase):
    @staticmethod
    def get_table_name(manager):
        return manager.model._meta.db_table

    def test_custom_table_name(self):
        self.assertEqual(self.get_table_name(Contact.history), "contacts_history")

    def test_custom_table_name_from_register(self):
        self.assertEqual(
            self.get_table_name(ContactRegister.history), "contacts_register_history"
        )


class ExcludeFieldsTest(TestCase):
    def test_restore_pollwithexclude(self):
        poll = PollWithExcludeFields.objects.create(
            question="what's up?", pub_date=today
        )
        historical = poll.history.order_by("pk")[0]
        with self.assertRaises(AttributeError):
            historical.pub_date
        original = historical.instance
        self.assertEqual(original.pub_date, poll.pub_date)


class ExcludeFieldsForDeletedObjectTest(TestCase):
    def setUp(self):
        self.poll = PollWithExcludedFieldsWithDefaults.objects.create(
            question="what's up?", pub_date=today, max_questions=12
        )
        self.historical = self.poll.history.order_by("pk")[0]
        self.poll.delete()

    def test_restore_deleted_poll_exclude_fields(self):
        original = self.historical.instance
        # pub_date don't have default value so it will be None
        self.assertIsNone(original.pub_date)
        # same for max_questions
        self.assertIsNone(original.max_questions)

    def test_restore_deleted_poll_exclude_fields_with_defaults(self):
        poll = self.poll
        original = self.historical.instance
        self.assertEqual(original.expiration_time, poll.expiration_time)
        self.assertEqual(original.place, poll.place)
        self.assertEqual(original.min_questions, poll.min_questions)


class ExcludeForeignKeyTest(TestCase):
    def setUp(self):
        self.poll = PollWithExcludedFKField.objects.create(
            question="Is it?",
            pub_date=today,
            place=Place.objects.create(name="Somewhere"),
        )

    def get_first_historical(self):
        """
        Retrieve the idx'th HistoricalPoll, ordered by time.
        """
        return self.poll.history.order_by("history_date")[0]

    def test_instance_fk_value(self):
        historical = self.get_first_historical()
        original = historical.instance
        self.assertEqual(original.place, self.poll.place)

    def test_history_lacks_fk(self):
        historical = self.get_first_historical()
        with self.assertRaises(AttributeError):
            historical.place

    def test_nb_queries(self):
        with self.assertNumQueries(2):
            historical = self.get_first_historical()
            historical.instance

    def test_changed_value_lost(self):
        new_place = Place.objects.create(name="More precise")
        self.poll.place = new_place
        self.poll.save()
        historical = self.get_first_historical()
        instance = historical.instance
        self.assertEqual(instance.place, new_place)


def add_static_history_ip_address(sender, **kwargs):
    history_instance = kwargs["history_instance"]
    history_instance.ip_address = "192.168.0.1"


class ExtraFieldsStaticIPAddressTestCase(TestCase):
    def setUp(self):
        pre_create_historical_record.connect(
            add_static_history_ip_address,
            sender=HistoricalPollWithHistoricalIPAddress,
            dispatch_uid="add_static_history_ip_address",
        )

    def tearDown(self):
        pre_create_historical_record.disconnect(
            add_static_history_ip_address,
            sender=HistoricalPollWithHistoricalIPAddress,
            dispatch_uid="add_static_history_ip_address",
        )

    def test_extra_ip_address_field_populated_on_save(self):
        poll = PollWithHistoricalIPAddress.objects.create(
            question="Will it blend?", pub_date=today
        )

        poll_history = poll.history.first()

        self.assertEqual("192.168.0.1", poll_history.ip_address)

    def test_extra_ip_address_field_not_present_on_poll(self):
        poll = PollWithHistoricalIPAddress.objects.create(
            question="Will it blend?", pub_date=today
        )

        with self.assertRaises(AttributeError):
            poll.ip_address


def add_dynamic_history_ip_address(sender, **kwargs):
    history_instance = kwargs["history_instance"]
    history_instance.ip_address = HistoricalRecords.context.request.META["REMOTE_ADDR"]


@override_settings(**middleware_override_settings)
class ExtraFieldsDynamicIPAddressTestCase(TestCase):
    def setUp(self):
        pre_create_historical_record.connect(
            add_dynamic_history_ip_address,
            sender=HistoricalPollWithHistoricalIPAddress,
            dispatch_uid="add_dynamic_history_ip_address",
        )

    def tearDown(self):
        pre_create_historical_record.disconnect(
            add_dynamic_history_ip_address,
            sender=HistoricalPollWithHistoricalIPAddress,
            dispatch_uid="add_dynamic_history_ip_address",
        )

    def test_signal_is_able_to_retrieve_request_from_context(self):
        data = {"question": "Will it blend?", "pub_date": "2018-10-30"}

        self.client.post(reverse("pollip-add"), data=data)

        polls = PollWithHistoricalIPAddress.objects.all()
        self.assertEqual(1, polls.count())

        poll_history = polls[0].history.first()
        self.assertEqual("127.0.0.1", poll_history.ip_address)


class WarningOnAbstractModelWithInheritFalseTest(TestCase):
    def test_warning_on_abstract_model_with_inherit_false(self):
        with warnings.catch_warnings(record=True) as w:

            class AbstractModelWithInheritFalse(models.Model):
                string = models.CharField()
                history = HistoricalRecords()

                class Meta:
                    abstract = True

            self.assertEqual(len(w), 1)
            self.assertTrue(issubclass(w[0].category, UserWarning))
            self.assertEqual(
                str(w[0].message),
                "HistoricalRecords added to abstract model "
                "(AbstractModelWithInheritFalse) without "
                "inherit=True",
            )


class MultiDBWithUsingTest(TestCase):
    """Asserts historical manager respects `using()` and the `using`
    keyword argument in `save()`.
    """

    databases = {"default", "other"}

    db_name = "other"

    def test_multidb_with_using_not_on_default(self):
        model = ModelWithHistoryUsingBaseModelDb.objects.using(self.db_name).create(
            name="1-84356-028-1"
        )
        self.assertRaises(ObjectDoesNotExist, model.history.get, name="1-84356-028-1")

    def test_multidb_with_using_is_on_dbtwo(self):
        model = ModelWithHistoryUsingBaseModelDb.objects.using(self.db_name).create(
            name="1-84356-028-1"
        )
        try:
            model.history.using(self.db_name).get(name="1-84356-028-1")
        except ObjectDoesNotExist:
            self.fail("ObjectDoesNotExist unexpectedly raised.")

    def test_multidb_with_using_and_fk_not_on_default(self):
        model = ModelWithHistoryUsingBaseModelDb.objects.using(self.db_name).create(
            name="1-84356-028-1"
        )
        parent_model = ModelWithFkToModelWithHistoryUsingBaseModelDb.objects.using(
            self.db_name
        ).create(fk=model)
        self.assertRaises(ObjectDoesNotExist, parent_model.history.get, fk=model)

    def test_multidb_with_using_and_fk_on_dbtwo(self):
        model = ModelWithHistoryUsingBaseModelDb.objects.using(self.db_name).create(
            name="1-84356-028-1"
        )
        parent_model = ModelWithFkToModelWithHistoryUsingBaseModelDb.objects.using(
            self.db_name
        ).create(fk=model)
        try:
            parent_model.history.using(self.db_name).get(fk=model)
        except ObjectDoesNotExist:
            self.fail("ObjectDoesNotExist unexpectedly raised.")

    def test_multidb_with_using_keyword_in_save_not_on_default(self):
        model = ModelWithHistoryUsingBaseModelDb(name="1-84356-028-1")
        model.save(using=self.db_name)
        self.assertRaises(ObjectDoesNotExist, model.history.get, name="1-84356-028-1")

    def test_multidb_with_using_keyword_in_save_on_dbtwo(self):
        model = ModelWithHistoryUsingBaseModelDb(name="1-84356-028-1")
        model.save(using=self.db_name)
        try:
            model.history.using(self.db_name).get(name="1-84356-028-1")
        except ObjectDoesNotExist:
            self.fail("ObjectDoesNotExist unexpectedly raised.")

    def test_multidb_with_using_keyword_in_save_with_fk(self):
        model = ModelWithHistoryUsingBaseModelDb(name="1-84356-028-1")
        model.save(using=self.db_name)
        parent_model = ModelWithFkToModelWithHistoryUsingBaseModelDb(fk=model)
        parent_model.save(using=self.db_name)
        # assert not created on default
        self.assertRaises(ObjectDoesNotExist, parent_model.history.get, fk=model)
        # assert created on dbtwo
        try:
            parent_model.history.using(self.db_name).get(fk=model)
        except ObjectDoesNotExist:
            self.fail("ObjectDoesNotExist unexpectedly raised.")

    def test_multidb_with_using_keyword_in_save_and_update(self):
        model = ModelWithHistoryUsingBaseModelDb.objects.using(self.db_name).create(
            name="1-84356-028-1"
        )
        model.save(using=self.db_name)
        self.assertEqual(
            ["+", "~"],
            [
                obj.history_type
                for obj in model.history.using(self.db_name)
                .all()
                .order_by("history_date")
            ],
        )

    def test_multidb_with_using_keyword_in_save_and_delete(self):
        HistoricalModelWithHistoryUseBaseModelDb = get_history_model_for_model(
            ModelWithHistoryUsingBaseModelDb
        )
        model = ModelWithHistoryUsingBaseModelDb.objects.using(self.db_name).create(
            name="1-84356-028-1"
        )
        model.save(using=self.db_name)
        model.delete(using=self.db_name)
        self.assertEqual(
            ["+", "~", "-"],
            [
                obj.history_type
                for obj in HistoricalModelWithHistoryUseBaseModelDb.objects.using(
                    self.db_name
                )
                .all()
                .order_by("history_date")
            ],
        )


class ForeignKeyToSelfTest(TestCase):
    def setUp(self):
        self.model = ForeignKeyToSelfModel
        self.history_model = self.model.history.model

    def test_foreign_key_to_self_using_model_str(self):
        self.assertEqual(
            self.model, self.history_model.fk_to_self.field.remote_field.model
        )

    def test_foreign_key_to_self_using_self_str(self):
        self.assertEqual(
            self.model, self.history_model.fk_to_self_using_str.field.remote_field.model
        )


@override_settings(**database_router_override_settings)
class MultiDBExplicitHistoryUserIDTest(TestCase):
    databases = {"default", "other"}

    def setUp(self):
        self.user = get_user_model().objects.create(  # nosec
            username="username", email="username@test.com", password="top_secret"
        )

    def test_history_user_with_fk_in_different_db_raises_value_error(self):
        instance = ExternalModel(name="random_name")
        instance._history_user = self.user
        with self.assertRaises(ValueError):
            instance.save()

    def test_history_user_with_integer_field(self):
        instance = ExternalModelWithCustomUserIdField(name="random_name")
        instance._history_user = self.user
        instance.save()

        self.assertEqual(self.user.id, instance.history.first().history_user_id)
        self.assertEqual(self.user, instance.history.first().history_user)

    def test_history_user_is_none(self):
        instance = ExternalModelWithCustomUserIdField.objects.create(name="random_name")

        self.assertIsNone(instance.history.first().history_user_id)
        self.assertIsNone(instance.history.first().history_user)

    def test_history_user_does_not_exist(self):
        instance = ExternalModelWithCustomUserIdField(name="random_name")
        instance._history_user = self.user
        instance.save()

        self.assertEqual(self.user.id, instance.history.first().history_user_id)
        self.assertEqual(self.user, instance.history.first().history_user)

        user_id = self.user.id
        self.user.delete()

        self.assertEqual(user_id, instance.history.first().history_user_id)
        self.assertIsNone(instance.history.first().history_user)


class RelatedNameTest(TestCase):
    def setUp(self):
        self.user_one = get_user_model().objects.create(  # nosec
            username="username_one", email="first@user.com", password="top_secret"
        )
        self.user_two = get_user_model().objects.create(  # nosec
            username="username_two", email="second@user.com", password="top_secret"
        )

        self.one = Street(name="Test Street")
        self.one._history_user = self.user_one
        self.one.save()

        self.two = Street(name="Sesame Street")
        self.two._history_user = self.user_two
        self.two.save()

        self.one.name = "ABC Street"
        self.one._history_user = self.user_two
        self.one.save()

    def test_relation(self):
        self.assertEqual(self.one.history.count(), 2)
        self.assertEqual(self.two.history.count(), 1)

    def test_filter(self):
        self.assertEqual(
            Street.objects.filter(history__history_user=self.user_one.pk).count(), 1
        )
        self.assertEqual(
            Street.objects.filter(history__history_user=self.user_two.pk).count(), 2
        )

    def test_name_equals_manager(self):
        with self.assertRaises(RelatedNameConflictError):
            register(Place, manager_name="history", related_name="history")

    def test_deletion(self):
        self.two.delete()

        self.assertEqual(Street.log.filter(history_relation=2).count(), 2)
        self.assertEqual(Street.log.count(), 4)

    def test_revert(self):
        id = self.one.pk

        self.one.delete()
        self.assertEqual(
            Street.objects.filter(history__history_user=self.user_one.pk).count(), 0
        )
        self.assertEqual(Street.objects.filter(pk=id).count(), 0)

        old = Street.log.filter(id=id).first()
        old.history_object.save()
        self.assertEqual(
            Street.objects.filter(history__history_user=self.user_one.pk).count(), 1
        )

        self.one = Street.objects.get(pk=id)
        self.assertEqual(self.one.history.count(), 4)


@override_settings(**database_router_override_settings_history_in_diff_db)
class SaveHistoryInSeparateDatabaseTestCase(TestCase):
    databases = {"default", "other"}

    def setUp(self):
        self.model = ModelWithHistoryInDifferentDb.objects.create(name="test")

    def test_history_model_saved_in_separate_db(self):
        self.assertEqual(0, self.model.history.using("default").count())
        self.assertEqual(1, self.model.history.count())
        self.assertEqual(1, self.model.history.using("other").count())
        self.assertEqual(
            1, ModelWithHistoryInDifferentDb.objects.using("default").count()
        )
        self.assertEqual(1, ModelWithHistoryInDifferentDb.objects.count())
        self.assertEqual(
            0, ModelWithHistoryInDifferentDb.objects.using("other").count()
        )

    def test_history_model_saved_in_separate_db_on_delete(self):
        id = self.model.id
        self.model.delete()

        self.assertEqual(
            0,
            ModelWithHistoryInDifferentDb.history.using("default")
            .filter(id=id)
            .count(),
        )
        self.assertEqual(2, ModelWithHistoryInDifferentDb.history.filter(id=id).count())
        self.assertEqual(
            2,
            ModelWithHistoryInDifferentDb.history.using("other").filter(id=id).count(),
        )
        self.assertEqual(
            0, ModelWithHistoryInDifferentDb.objects.using("default").count()
        )
        self.assertEqual(0, ModelWithHistoryInDifferentDb.objects.count())
        self.assertEqual(
            0, ModelWithHistoryInDifferentDb.objects.using("other").count()
        )


class ModelWithMultipleNoDBIndexTest(TestCase):
    def setUp(self):
        self.model = ModelWithMultipleNoDBIndex
        self.history_model = self.model.history.model

    def test_field_indices(self):
        for field in ["name", "fk"]:
            # dropped index
            self.assertTrue(self.model._meta.get_field(field).db_index)
            self.assertFalse(self.history_model._meta.get_field(field).db_index)

            # keeps index
            keeps_index = "%s_keeps_index" % field
            self.assertTrue(self.model._meta.get_field(keeps_index).db_index)
            self.assertTrue(self.history_model._meta.get_field(keeps_index).db_index)


class ModelWithSingleNoDBIndexUniqueTest(TestCase):
    def setUp(self):
        self.model = ModelWithSingleNoDBIndexUnique
        self.history_model = self.model.history.model

    def test_unique_field_index(self):
        # Ending up with deferred fields (dont know why), using work around
        self.assertTrue(self.model._meta.get_field("name").db_index)
        self.assertFalse(self.history_model._meta.get_field("name").db_index)

        # keeps index
        self.assertTrue(self.model._meta.get_field("name_keeps_index").db_index)
        self.assertTrue(self.history_model._meta.get_field("name_keeps_index").db_index)


class HistoricForeignKeyTest(TestCase):
    """
    Tests chasing foreign keys across time points naturally with
    HistoricForeignKey.
    """

    def test_non_historic_to_historic(self):
        """
        Non-historic table foreign key to historic table.

        In this case it should simply behave like ForeignKey because
        the origin model (this one) cannot be historic, so foreign key
        lookups are always "current".
        """
        org = TestOrganizationWithHistory.objects.create(name="original")
        part = TestParticipantToHistoricOrganization.objects.create(
            name="part", organization=org
        )
        before_mod = timezone.now()
        self.assertEqual(part.organization.id, org.id)
        self.assertEqual(org.participants.count(), 1)
        self.assertEqual(org.participants.all()[0], part)

        historg = TestOrganizationWithHistory.history.as_of(before_mod).get(
            name="original"
        )
        self.assertEqual(historg.participants.count(), 1)
        self.assertEqual(historg.participants.all()[0], part)

        self.assertEqual(org.history.count(), 1)
        org.name = "modified"
        org.save()
        self.assertEqual(org.history.count(), 2)

        # drop internal caches, re-select
        part = TestParticipantToHistoricOrganization.objects.get(name="part")
        self.assertEqual(part.organization.name, "modified")

    def test_historic_to_non_historic(self):
        """
        Historic table foreign key to non-historic table.

        In this case it should simply behave like ForeignKey because
        the origin model (this one) can be historic but the target model
        is not, so foreign key lookups are always "current".
        """
        org = TestOrganization.objects.create(name="org")
        part = TestHistoricParticipantToOrganization.objects.create(
            name="original", organization=org
        )
        self.assertEqual(part.organization.id, org.id)
        self.assertEqual(org.participants.count(), 1)
        self.assertEqual(org.participants.all()[0], part)

        histpart = TestHistoricParticipantToOrganization.objects.get(name="original")
        self.assertEqual(histpart.organization.id, org.id)

    def test_historic_to_historic(self):
        """
        Historic table foreign key to historic table.

        In this case as_of queries on the origin model (this one)
        or on the target model (the other one) will traverse the
        foreign key relationship honoring the timepoint of the
        original query.  This only happens when both tables involved
        are historic.

        At t1 we have one org, one participant.
        At t2 we have one org, two participants, however the org's name has changed.
        At t3 we have one org, and one participant has left.
        """
        org = TestOrganizationWithHistory.objects.create(name="original")
        p1 = TestHistoricParticipanToHistoricOrganization.objects.create(
            name="p1", organization=org
        )
        t1_one_participant = timezone.now()
        p2 = TestHistoricParticipanToHistoricOrganization.objects.create(
            name="p2", organization=org
        )
        org.name = "modified"
        org.save()
        t2_two_participants = timezone.now()
        p1.delete()
        t3_one_participant = timezone.now()

        # forward relationships - see how natural chasing timepoint relations is
        p1t1 = TestHistoricParticipanToHistoricOrganization.history.as_of(
            t1_one_participant
        ).get(name="p1")
        self.assertEqual(p1t1.organization, org)
        self.assertEqual(p1t1.organization.name, "original")
        p1t2 = TestHistoricParticipanToHistoricOrganization.history.as_of(
            t2_two_participants
        ).get(name="p1")
        self.assertEqual(p1t2.organization, org)
        self.assertEqual(p1t2.organization.name, "modified")
        p2t2 = TestHistoricParticipanToHistoricOrganization.history.as_of(
            t2_two_participants
        ).get(name="p2")
        self.assertEqual(p2t2.organization, org)
        self.assertEqual(p2t2.organization.name, "modified")
        p2t3 = TestHistoricParticipanToHistoricOrganization.history.as_of(
            t3_one_participant
        ).get(name="p2")
        self.assertEqual(p2t3.organization, org)
        self.assertEqual(p2t3.organization.name, "modified")

        # reverse relationships
        # at t1
        ot1 = TestOrganizationWithHistory.history.as_of(t1_one_participant).all()[0]
        self.assertEqual(ot1.historic_participants.count(), 1)
        self.assertEqual(ot1.historic_participants.all()[0].name, p1.name)
        # at t2
        ot2 = TestOrganizationWithHistory.history.as_of(t2_two_participants).all()[0]
        self.assertEqual(ot2.historic_participants.count(), 2)
        self.assertIn(p1.name, [item.name for item in ot2.historic_participants.all()])
        self.assertIn(p2.name, [item.name for item in ot2.historic_participants.all()])
        # at t3
        ot3 = TestOrganizationWithHistory.history.as_of(t3_one_participant).all()[0]
        self.assertEqual(ot3.historic_participants.count(), 1)
        self.assertEqual(ot3.historic_participants.all()[0].name, p2.name)
        # current
        self.assertEqual(org.historic_participants.count(), 1)
        self.assertEqual(org.historic_participants.all()[0].name, p2.name)

        self.assertTrue(is_historic(ot1))
        self.assertFalse(is_historic(org))

        self.assertIsInstance(
            to_historic(ot1), TestOrganizationWithHistory.history.model
        )
        self.assertIsNone(to_historic(org))
