from django.db import models

from phonenumber_field.modelfields import PhoneNumberField  # type: ignore

from .utility.time_stamped_model import TimeStampedModel


class Contact(TimeStampedModel):

    """Contact information follows a similar pattern for each contact."""

    user = models.OneToOneField(
        "registrar.User",
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
    )

    first_name = models.TextField(
        null=True,
        blank=True,
        help_text="First name",
        verbose_name="first name / given name",
        db_index=True,
    )
    middle_name = models.TextField(
        null=True,
        blank=True,
        help_text="Middle name (optional)",
    )
    last_name = models.TextField(
        null=True,
        blank=True,
        help_text="Last name",
        verbose_name="last name / family name",
        db_index=True,
    )
    title = models.TextField(
        null=True,
        blank=True,
        help_text="Title",
        verbose_name="title or role in your organization",
    )
    email = models.EmailField(
        null=True,
        blank=True,
        help_text="Email",
        db_index=True,
    )
    phone = PhoneNumberField(
        null=True,
        blank=True,
        help_text="Phone",
        db_index=True,
    )

    def get_formatted_name(self):
        """Returns the contact's name in Western order."""
        names = [n for n in [self.first_name, self.middle_name, self.last_name] if n]
        return " ".join(names) if names else "Unknown"

    def save(self, *args, **kwargs):
        # Call the parent class's save method to perform the actual save
        super().save(*args, **kwargs)

        # Update the related User object's first_name and last_name
        if self.user:
            self.user.first_name = self.first_name
            self.user.last_name = self.last_name
            self.user.save()

    def __str__(self):
        if self.first_name or self.last_name:
            return self.get_formatted_name()
        elif self.email:
            return self.email
        elif self.pk:
            return str(self.pk)
        else:
            return ""

src/registrar/models/contact.py
from django.db import models

from phonenumber_field.modelfields import PhoneNumberField  # type: ignore

from .utility.time_stamped_model import TimeStampedModel


class Contact(TimeStampedModel):

    """Contact information follows a similar pattern for each contact."""

    user = models.OneToOneField(
        "registrar.User",
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
    )

    first_name = models.TextField(
        null=True,
        blank=True,
        help_text="First name",
        verbose_name="first name / given name",
        db_index=True,
    )
    middle_name = models.TextField(
        null=True,
        blank=True,
        help_text="Middle name (optional)",
    )
    last_name = models.TextField(
        null=True,
        blank=True,
        help_text="Last name",
        verbose_name="last name / family name",
        db_index=True,
    )
    title = models.TextField(
        null=True,
        blank=True,
        help_text="Title",
        verbose_name="title or role in your organization",
    )
    email = models.EmailField(
        null=True,
        blank=True,
        help_text="Email",
        db_index=True,
    )
    phone = PhoneNumberField(
        null=True,
        blank=True,
        help_text="Phone",
        db_index=True,
    )

    def get_formatted_name(self):
        """Returns the contact's name in Western order."""
        names = [n for n in [self.first_name, self.middle_name, self.last_name] if n]
        return " ".join(names) if names else "Unknown"

    def save(self, *args, **kwargs):
        # Call the parent class's save method to perform the actual save
        super().save(*args, **kwargs)

        # Update the related User object's first_name and last_name
        if self.user and (not self.user.first_name or not self.user.last_name):
            self.user.first_name = self.first_name
            self.user.last_name = self.last_name
            self.user.save()

    def __str__(self):
        if self.first_name or self.last_name:
            return self.get_formatted_name()
        elif self.email:
            return self.email
        elif self.pk:
            return str(self.pk)
        else:
            return ""

src/registrar/models/contact.py
diff --git a/src/registrar/models/contact.py b/src/registrar/models/contact.py
index 6b3b6ddb2..06cf83887 100644
--- a/src/registrar/models/contact.py
+++ b/src/registrar/models/contact.py
@@ -64,7 +64,7 @@ def save(self, *args, **kwargs):
         super().save(*args, **kwargs)
 
         # Update the related User object's first_name and last_name
-        if self.user:
+        if self.user and (not self.user.first_name or not self.user.last_name):
             self.user.first_name = self.first_name
             self.user.last_name = self.last_name
             self.user.save()
diff --git a/src/registrar/tests/test_admin.py b/src/registrar/tests/test_admin.py
index 9d6add249..8c8dd5ab5 100644
--- a/src/registrar/tests/test_admin.py
+++ b/src/registrar/tests/test_admin.py
@@ -1110,8 +1110,8 @@ def test_alphabetically_sorted_fk_fields_domain_application(self):
         tested_fields = [
             DomainApplication.authorizing_official.field,
             DomainApplication.submitter.field,
-            # DomainApplication.investigator.field,
-            # DomainApplication.creator.field,
+            DomainApplication.investigator.field,
+            DomainApplication.creator.field,
             DomainApplication.requested_domain.field,
         ]
 
diff --git a/src/registrar/tests/test_models.py b/src/registrar/tests/test_models.py
index 0e0839382..96f78bf9f 100644
--- a/src/registrar/tests/test_models.py
+++ b/src/registrar/tests/test_models.py
@@ -672,6 +672,12 @@ def test_check_domain_invitations_on_login_caps_email(self):
 
 class TestContact(TestCase):
     def setUp(self):
+        self.email_for_invalid = "intern@igorville.gov"
+        self.invalid_user, _ = User.objects.get_or_create(
+            username=self.email_for_invalid, email=self.email_for_invalid, first_name="", last_name=""
+        )
+        self.invalid_contact, _ = Contact.objects.get_or_create(user=self.invalid_user)
+
         self.email = "mayor@igorville.gov"
         self.user, _ = User.objects.get_or_create(email=self.email, first_name="Jeff", last_name="Lebowski")
         self.contact, _ = Contact.objects.get_or_create(user=self.user)
@@ -683,6 +689,31 @@ def tearDown(self):
 
     def test_saving_contact_updates_user_first_last_names(self):
         """When a contact is updated, we propagate the changes to the linked user if it exists."""
+
+        # User and Contact are created and linked as expected.
+        # An empty User object should create an empty contact.
+        self.assertEqual(self.invalid_contact.first_name, "")
+        self.assertEqual(self.invalid_contact.last_name, "")
+        self.assertEqual(self.invalid_user.first_name, "")
+        self.assertEqual(self.invalid_user.last_name, "")
+
+        # Manually update the contact - mimicking production (pre-existing data)
+        self.invalid_contact.first_name = "Joey"
+        self.invalid_contact.last_name = "Baloney"
+        self.invalid_contact.save()
+
+        # Refresh the user object to reflect the changes made in the database
+        self.invalid_user.refresh_from_db()
+
+        # Updating the contact's first and last names propagate to the user
+        self.assertEqual(self.invalid_contact.first_name, "Joey")
+        self.assertEqual(self.invalid_contact.last_name, "Baloney")
+        self.assertEqual(self.invalid_user.first_name, "Joey")
+        self.assertEqual(self.invalid_user.last_name, "Baloney")
+
+    def test_saving_contact_does_not_update_user_first_last_names(self):
+        """When a contact is updated, we avoid propagating the changes to the linked user if it already has a value"""
+
         # User and Contact are created and linked as expected
         self.assertEqual(self.contact.first_name, "Jeff")
         self.assertEqual(self.contact.last_name, "Lebowski")
@@ -699,11 +730,11 @@ def test_saving_contact_updates_user_first_last_names(self):
         # Updating the contact's first and last names propagate to the user
         self.assertEqual(self.contact.first_name, "Joey")
         self.assertEqual(self.contact.last_name, "Baloney")
-        self.assertEqual(self.user.first_name, "Joey")
-        self.assertEqual(self.user.last_name, "Baloney")
+        self.assertEqual(self.user.first_name, "Jeff")
+        self.assertEqual(self.user.last_name, "Lebowski")
 
     def test_saving_contact_does_not_update_user_email(self):
-        """When a contact's email is updated, the change is not propagated to the lined user."""
+        """When a contact's email is updated, the change is not propagated to the user."""
         self.contact.email = "joey.baloney@diaperville.com"
         self.contact.save()
 
@@ -713,3 +744,16 @@ def test_saving_contact_does_not_update_user_email(self):
         # Updating the contact's email does not propagate
         self.assertEqual(self.contact.email, "joey.baloney@diaperville.com")
         self.assertEqual(self.user.email, "mayor@igorville.gov")
+
+    def test_saving_contact_does_not_update_user_email_when_none(self):
+        """When a contact's email is updated, and the first/last name is none,
+        the change is not propagated to the user."""
+        self.invalid_contact.email = "joey.baloney@diaperville.com"
+        self.invalid_contact.save()
+
+        # Refresh the user object to reflect the changes made in the database
+        self.invalid_user.refresh_from_db()
+
+        # Updating the contact's email does not propagate
+        self.assertEqual(self.invalid_contact.email, "joey.baloney@diaperville.com")
+        self.assertEqual(self.invalid_user.email, "intern@igorville.gov")
diff --git a/src/registrar/tests/test_models_domain.py b/src/registrar/tests/test_models_domain.py
index 39f63c942..d21aabb9e 100644
--- a/src/registrar/tests/test_models_domain.py
+++ b/src/registrar/tests/test_models_domain.py
@@ -761,7 +761,6 @@ def test_not_disclosed_on_other_contacts(self):
             self.assertEqual(expected_contact.email, actual_contact.email)
 
     def test_convert_public_contact_to_epp(self):
-        self.maxDiff = None
         domain, _ = Domain.objects.get_or_create(name="freeman.gov")
         dummy_contact = domain.get_default_security_contact()
         test_disclose = self._convertPublicContactToEpp(dummy_contact, disclose_email=True).__dict__
diff --git a/src/registrar/tests/test_reports.py b/src/registrar/tests/test_reports.py
index 112b2ba34..4b854a0a0 100644
--- a/src/registrar/tests/test_reports.py
+++ b/src/registrar/tests/test_reports.py
@@ -152,7 +152,6 @@ def side_effect(Bucket, Key):
     @boto3_mocking.patching
     def test_load_federal_report(self):
         """Tests the get_current_federal api endpoint"""
-        self.maxDiff = None
         mock_client = MagicMock()
         mock_client_instance = mock_client.return_value
 

User/Contact bug: Signals double saving, overwriting data for test cases
### Current Behavior

A bug was introduced [with a PR](https://github.com/cisagov/manage.get.gov/pull/1491) that fixed our oidc login clearing out user information. To fix this, the Contact object was linked to the User object, such that they would remain in sync.

However, this introduced a sneaky double-save bug wherein if signals get called more than once for the same contact object (as we found happens in our test cases), it overrides information when it should not do so.

![image](https://github.com/cisagov/manage.get.gov/assets/141044360/f9d35949-5138-4337-a189-9e2a843191f5)
![image](https://github.com/cisagov/manage.get.gov/assets/141044360/917e6db7-6cfd-48c8-a9b0-e0b8f01f2831)

### Expected Behavior

When a `Contact` object is saved, the `save()` function checks to see if `self.user` is not None. When it is not, it will update the  `self.user` object with whatever value is located at `self`. It appears that in certain situations, (such as our test cases),  the `User` field is behaving as if it were a one-to-many relationship (one Contact object to many User objects). This should not be the case, and `Contact` should only update one `User` field.

### Steps to Reproduce

1. Create a new test case in `test_admin.py`, and create 3 fake User objects. Populate them with unique data. Then, log their values. Note that their data seems to get overwritten.


### Environment

_No response_

### Additional Context

This bug seems to be originating in the `signals.py` file in the ` handle_profile` class. By passing in a flag that disables the save behavior (or just commenting it out), this issue seems to resolve. 
![image](https://github.com/cisagov/manage.get.gov/assets/141044360/e19a1508-72c7-405a-96db-3540305dcc4b)


### Issue Links

🔄 Relates to: [#1464 / #1468](https://github.com/cisagov/manage.get.gov/pull/1491)
🔄 Relates to: [this PR](https://github.com/cisagov/manage.get.gov/pull/1543) (pinpoints where the behavior is occurring but does not solve it)
