"""Synthetic Customer foundation integrity, HTTP and PostgreSQL race tests."""
import uuid
from unittest.mock import patch

from django.contrib import admin
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Permission
from django.core.exceptions import ValidationError
from django.db import IntegrityError, transaction
from django.db.models.deletion import ProtectedError
from django.test import Client, TestCase, TransactionTestCase
from django.urls import reverse

from apps.access.authorization import is_authorized, authorized_queryset
from apps.organization import services as organization_services
from apps.organization import test_assignment_concurrency as concurrency
from apps.organization.models import Company
from .models import Customer, CustomerNumberSequence, format_customer_number
from .queries import active_customers_for_company, find_customers
from . import services


def make_company(code="CUSTOMER"):
    return Company.objects.create(code=code, name="Synthetic company")


def create(company, **kwargs):
    return services.create_customer(company=company, customer_type=Customer.Type.INDIVIDUAL,
                                    full_name="Synthetic Person", **kwargs)


class CustomerTests(TestCase):
    def setUp(self):
        self.company = make_company()
        self.other = make_company("OTHER")

    def test_identity_defaults_and_independence_from_users(self):
        customer = create(self.company)
        self.assertIsInstance(customer.pk, uuid.UUID)
        self.assertTrue(customer.is_active)
        self.assertIsNotNone(customer.created_at)
        self.assertIsNotNone(customer.updated_at)
        self.assertEqual(customer.company, self.company)
        self.assertEqual(customer.display_name, "Synthetic Person")
        self.assertEqual(str(customer), "CUS-00000001")
        self.assertEqual(get_user_model().objects.count(), 0)

    def test_lazy_sequence_first_sequential_and_company_separation(self):
        self.assertEqual(CustomerNumberSequence.objects.count(), 0)
        first, second, other = create(self.company), create(self.company), create(self.other)
        self.assertEqual([first.customer_number, second.customer_number, other.customer_number],
                         ["CUS-00000001", "CUS-00000002", "CUS-00000001"])
        self.assertEqual(CustomerNumberSequence.objects.get(company=self.company).next_value, 3)

    def test_failed_creation_rolls_back_lazy_sequence(self):
        with self.assertRaises(ValidationError):
            services.create_customer(company=self.company, customer_type="INVALID", full_name="Synthetic")
        self.assertFalse(Customer.objects.exists())
        self.assertFalse(CustomerNumberSequence.objects.exists())
        self.assertEqual(create(self.company).customer_number, "CUS-00000001")

    def test_outer_rollback_restores_existing_sequence(self):
        create(self.company)
        with self.assertRaises(RuntimeError), transaction.atomic():
            create(self.company)
            raise RuntimeError("Synthetic transaction failure")
        self.assertEqual(create(self.company).customer_number, "CUS-00000002")

    def test_deletion_does_not_reuse_committed_number(self):
        create(self.company).delete()
        self.assertEqual(create(self.company).customer_number, "CUS-00000002")

    def test_allocator_capacity_and_format(self):
        self.assertEqual(format_customer_number(100000000), "CUS-100000000")
        for value in (0, -1, 1000000000000000000):
            with self.subTest(value=value), self.assertRaises(ValidationError):
                format_customer_number(value)

    def test_individual_requires_usable_name_and_no_organization_name(self):
        for name, organization in [("", ""), (" \t\n ", ""), ("Synthetic", "Organization")]:
            with self.subTest(name=name), self.assertRaises(ValidationError):
                services.create_customer(company=self.company, customer_type="INDIVIDUAL",
                                         full_name=name, organization_name=organization)

    def test_organization_requires_own_name_and_allows_optional_full_name(self):
        for name in ("", " \t "):
            with self.assertRaises(ValidationError):
                services.create_customer(company=self.company, customer_type="ORGANIZATION", organization_name=name)
        customer = services.create_customer(company=self.company, customer_type="ORGANIZATION",
                                            organization_name="  Synthetic Organization  ")
        self.assertEqual(customer.full_name, "")
        self.assertEqual(customer.display_name, "Synthetic Organization")

    def test_contact_normalization_and_duplicate_contacts(self):
        for company in (self.company, self.company, self.other):
            customer = create(company, primary_mobile=" +1 (202) 555-0123 ", primary_email=" Contact@EXAMPLE.COM ")
            self.assertEqual(customer.primary_mobile, "+12025550123")
            self.assertEqual(customer.primary_email, "Contact@example.com")
        self.assertEqual(Customer.objects.count(), 3)

    def test_mobile_validation_and_local_number_preservation(self):
        customer = create(self.company, primary_mobile="020 7946 0123")
        self.assertEqual(customer.primary_mobile, "02079460123")
        for mobile in ("abc", "123", "+0123456789", "12+34567890", "1234567890123456", "１２３４５６７８９", "123\n456789"):
            with self.subTest(mobile=mobile), self.assertRaises(ValidationError):
                services.update_customer(customer=customer, primary_mobile=mobile)
        customer.refresh_from_db()
        self.assertEqual(customer.primary_mobile, "02079460123")

    def test_email_validation_optional_contacts_and_explicit_clear(self):
        customer = create(self.company)
        self.assertEqual((customer.primary_email, customer.primary_mobile), ("", ""))
        with self.assertRaises(ValidationError):
            services.update_customer(customer=customer, primary_email="invalid")
        services.update_customer(customer=customer, primary_email="synthetic@example.com")
        self.assertEqual(services.update_customer(customer=customer, primary_email="").primary_email, "")

    def test_database_constraints_for_type_number_uniqueness_and_names(self):
        customer = create(self.company)
        other = create(self.company)
        for changes in ({"customer_type": "INVALID"}, {"customer_number": customer.customer_number},
                        {"customer_number": "cus-00000003"}, {"customer_number": "CUS-00000003\n"},
                        {"customer_number": ""}, {"full_name": ""}, {"organization_name": "Wrong type"}):
            with self.subTest(changes=changes), self.assertRaises(IntegrityError), transaction.atomic():
                Customer.objects.filter(pk=other.pk).update(**changes)
        with self.assertRaises(IntegrityError), transaction.atomic():
            CustomerNumberSequence.objects.filter(company=self.company).update(next_value=0)

    def test_company_and_number_immutable_in_full_and_partial_saves(self):
        customer = create(self.company)
        for field, value in (("company", self.other), ("customer_number", "CUS-00000099")):
            for partial in (False, True):
                customer.refresh_from_db()
                setattr(customer, field, value)
                with self.subTest(field=field, partial=partial), self.assertRaises(ValidationError):
                    customer.save(update_fields=[field] if partial else None)
        with self.assertRaises(TypeError):
            services.update_customer(customer=customer, company=self.other)

    def test_direct_creation_uses_allocator_and_rejects_manual_number(self):
        customer = Customer(company=self.company, customer_type="INDIVIDUAL", full_name="Synthetic")
        customer.save()
        self.assertEqual(customer.customer_number, "CUS-00000001")
        with self.assertRaises(ValidationError):
            Customer.objects.create(company=self.company, customer_type="INDIVIDUAL", full_name="Synthetic",
                                    customer_number="CUS-00000099")

    def test_company_delete_protects_customers_and_allocator(self):
        customer = create(self.company)
        with self.assertRaises(ProtectedError):
            self.company.delete()
        customer.delete()
        with self.assertRaises(ProtectedError):
            self.company.delete()

    def test_creation_rejects_inactive_unsaved_and_deleted_company(self):
        organization_services.deactivate_company(company=self.company)
        for company in (self.company, Company(code="UNSAVED", name="Synthetic"), None):
            with self.assertRaises(ValidationError):
                create(company)
        deleted = make_company("DELETED")
        Company.objects.filter(pk=deleted.pk).delete()
        with self.assertRaises(ValidationError):
            create(deleted)

    def test_lifecycle_idempotence_and_stale_patch_preserves_other_fields(self):
        customer = create(self.company)
        first = services.deactivate_customer(customer=customer)
        second = services.deactivate_customer(customer=customer)
        self.assertEqual(first.updated_at, second.updated_at)
        services.update_customer(customer=customer, primary_email="synthetic@example.com")
        updated = services.update_customer(customer=customer, full_name="Corrected Name")
        self.assertFalse(updated.is_active)
        self.assertEqual(updated.primary_email, "synthetic@example.com")
        first = services.reactivate_customer(customer=customer)
        second = services.reactivate_customer(customer=customer)
        self.assertTrue(second.is_active)
        self.assertEqual(first.updated_at, second.updated_at)

    def test_partial_model_save_preserves_current_lifecycle_state(self):
        customer = create(self.company)
        services.deactivate_customer(customer=customer)
        customer.full_name = "Corrected Name"
        customer.save(update_fields=["full_name"])
        customer.refresh_from_db()
        self.assertFalse(customer.is_active)

    def test_company_deactivation_filters_without_mass_customer_updates(self):
        customer = create(self.company)
        organization_services.deactivate_company(company=self.company)
        customer.refresh_from_db()
        self.assertTrue(customer.is_active)
        self.assertFalse(active_customers_for_company(self.company).exists())
        self.assertFalse(find_customers(company=self.company, query="Synthetic").exists())
        updated = services.update_customer(customer=customer, full_name="Correction while unavailable")
        self.assertTrue(updated.is_active)
        services.deactivate_customer(customer=customer)
        with self.assertRaises(ValidationError):
            services.reactivate_customer(customer=customer)
        customer.is_active = True
        with self.assertRaises(ValidationError):
            customer.save(update_fields=["is_active"])
        organization_services.reactivate_company(company=self.company)
        self.assertFalse(active_customers_for_company(self.company).exists())
        services.reactivate_customer(customer=customer)
        self.assertEqual(list(active_customers_for_company(self.company)), [customer])

    def test_company_reactivation_restores_customers_whose_own_flag_stayed_active(self):
        customer = create(self.company)
        before = customer.updated_at
        organization_services.deactivate_company(company=self.company)
        organization_services.reactivate_company(company=self.company)
        customer.refresh_from_db()
        self.assertEqual(customer.updated_at, before)
        self.assertEqual(list(active_customers_for_company(self.company)), [customer])

    def test_search_isolation_all_fields_and_inactive_exclusion(self):
        customer = create(self.company, primary_mobile="+12025550123", primary_email="synthetic@example.com")
        create(self.other, primary_mobile="+12025550123", primary_email="synthetic@example.com")
        for query in ("CUS-00000001", "Synthetic", "+1 (202) 555-0123", "synthetic@example.com"):
            self.assertEqual(list(find_customers(company=self.company, query=query)), [customer])
        organization = services.create_customer(company=self.company, customer_type="ORGANIZATION",
                                                organization_name="Test Office")
        self.assertEqual(list(find_customers(company=self.company, query="Office")), [organization])
        services.deactivate_customer(customer=customer)
        self.assertFalse(find_customers(company=self.company, query="Synthetic").exists())
        for invalid in (None, Company(code="UNSAVED", name="Synthetic")):
            self.assertFalse(active_customers_for_company(invalid).exists())
        self.assertFalse(find_customers(company=self.company, query=" ").exists())

    def test_queries_are_lazy_fresh_and_one_sql_statement(self):
        create(self.company)
        with self.assertNumQueries(0):
            queryset = active_customers_for_company(self.company)
        organization_services.deactivate_company(company=self.company)
        with self.assertNumQueries(1):
            self.assertEqual(list(queryset), [])

    def test_customer_does_not_extend_scope_authorization(self):
        customer = create(self.company)
        user = get_user_model().objects.create_superuser(username="synthetic-admin", password="test-only-password")
        self.assertTrue(user.has_perm("customers.view_customer"))
        self.assertFalse(is_authorized(user=user, permission="customers.view_customer", target=customer))
        self.assertFalse(authorized_queryset(user=user, permission="customers.view_customer", queryset=Customer.objects.all()).exists())


class CustomerAdminTests(TestCase):
    @classmethod
    def setUpTestData(cls):
        cls.company = make_company()
        cls.other = make_company("OTHER")
        cls.user = get_user_model().objects.create_superuser(username="customer-admin", password="test-only-password")

    def setUp(self):
        self.client.force_login(self.user)

    def payload(self, **kwargs):
        return {"company": str(self.company.pk), "customer_type": "INDIVIDUAL",
                "full_name": "Synthetic Person", "organization_name": "", "primary_mobile": "",
                "primary_email": "", "_save": "Save", **kwargs}

    def test_admin_creation_generates_number_and_ignores_forged_lifecycle(self):
        response = self.client.post(reverse("admin:customers_customer_add"),
                                    self.payload(customer_number="CUS-99999999", is_active=""))
        self.assertEqual(response.status_code, 302)
        customer = Customer.objects.get()
        self.assertEqual(customer.customer_number, "CUS-00000001")
        self.assertTrue(customer.is_active)

    def test_admin_type_validation_has_no_customer_or_sequence_side_effect(self):
        response = self.client.post(reverse("admin:customers_customer_add"), self.payload(full_name=" "))
        self.assertEqual(response.status_code, 200)
        self.assertFalse(Customer.objects.exists())
        self.assertFalse(CustomerNumberSequence.objects.exists())

    def test_admin_ownership_number_type_and_active_fields_are_readonly(self):
        customer = create(self.company)
        response = self.client.post(reverse("admin:customers_customer_change", args=[customer.pk]),
            self.payload(company=str(self.other.pk), customer_number="CUS-99999999", customer_type="ORGANIZATION", full_name="Corrected"))
        self.assertEqual(response.status_code, 302)
        customer.refresh_from_db()
        self.assertEqual(customer.company_id, self.company.pk)
        self.assertEqual(customer.customer_number, "CUS-00000001")
        self.assertEqual(customer.customer_type, "INDIVIDUAL")
        self.assertTrue(customer.is_active)
        self.assertEqual(customer.full_name, "Corrected")

    def test_stale_admin_edit_preserves_deactivation_and_unedited_contact(self):
        customer = create(self.company)
        registered = admin.site._registry[Customer]
        original = registered.save_model
        def interleave(request, obj, form, change):
            services.deactivate_customer(customer=customer)
            services.update_customer(customer=customer, primary_email="new@example.com")
            return original(request, obj, form, change)
        with patch.object(registered, "save_model", side_effect=interleave):
            response = self.client.post(reverse("admin:customers_customer_change", args=[customer.pk]),
                                        self.payload(full_name="Corrected"))
        self.assertEqual(response.status_code, 302)
        customer.refresh_from_db()
        self.assertFalse(customer.is_active)
        self.assertEqual(customer.primary_email, "new@example.com")
        self.assertEqual(customer.full_name, "Corrected")

    def test_admin_lifecycle_actions_and_inactive_company_reactivation(self):
        customer = create(self.company)
        url = reverse("admin:customers_customer_changelist")
        for action, active in (("deactivate_selected", False), ("reactivate_selected", True)):
            self.assertEqual(self.client.post(url, {"action": action, "_selected_action": str(customer.pk)}).status_code, 302)
            customer.refresh_from_db()
            self.assertEqual(customer.is_active, active)
        services.deactivate_customer(customer=customer)
        organization_services.deactivate_company(company=self.company)
        self.assertEqual(self.client.post(url, {"action": "reactivate_selected", "_selected_action": str(customer.pk)}).status_code, 302)
        customer.refresh_from_db()
        self.assertFalse(customer.is_active)

    def test_admin_delete_disabled_and_view_only_cannot_change(self):
        customer = create(self.company)
        self.assertEqual(self.client.post(reverse("admin:customers_customer_delete", args=[customer.pk]), {"post": "yes"}).status_code, 403)
        reader = get_user_model().objects.create_user(username="customer-reader", is_staff=True)
        reader.user_permissions.add(Permission.objects.get(content_type__app_label="customers", codename="view_customer"))
        self.client.force_login(reader)
        self.assertEqual(self.client.post(reverse("admin:customers_customer_change", args=[customer.pk]), self.payload(full_name="Forbidden")).status_code, 403)
        self.assertTrue(Customer.objects.filter(pk=customer.pk).exists())

    def test_admin_csrf_required(self):
        client = Client(enforce_csrf_checks=True)
        client.force_login(self.user)
        self.assertEqual(client.post(reverse("admin:customers_customer_add"), self.payload()).status_code, 403)


class CustomerConcurrencyTests(TransactionTestCase):
    run_concurrent = concurrency.AssignmentConcurrencyTests.run_concurrent

    def setUp(self):
        self.company = make_company()
        self.other = make_company("OTHER")

    def test_simultaneous_first_creations_serialize_and_allocate_distinct_numbers(self):
        self.run_concurrent(lambda: create(self.company), lambda: create(self.company), expected="success")
        self.assertEqual(list(Customer.objects.values_list("customer_number", flat=True)), ["CUS-00000001", "CUS-00000002"])
        self.assertEqual(CustomerNumberSequence.objects.get(company=self.company).next_value, 3)

    def test_separate_companies_do_not_block_allocations(self):
        self.run_concurrent(lambda: create(self.company), lambda: create(self.other), expected="success", should_block=False)
        self.assertEqual(list(Customer.objects.values_list("customer_number", flat=True)), ["CUS-00000001", "CUS-00000001"])

    def test_company_deactivation_blocks_then_rejects_creation(self):
        self.run_concurrent(lambda: organization_services.deactivate_company(company=self.company),
                            lambda: create(self.company), expected="validation")
        self.assertFalse(Customer.objects.exists())
        self.assertFalse(CustomerNumberSequence.objects.exists())

    def test_creation_then_company_deactivation_preserves_record_but_hides_it(self):
        self.run_concurrent(lambda: create(self.company),
                            lambda: organization_services.deactivate_company(company=self.company), expected="success")
        self.assertTrue(Customer.objects.get().is_active)
        self.assertFalse(active_customers_for_company(self.company).exists())

    def test_concurrent_partial_updates_preserve_both_changes(self):
        customer = create(self.company)
        self.run_concurrent(lambda: services.update_customer(customer=customer, full_name="Corrected"),
                            lambda: services.update_customer(customer=customer, primary_email="new@example.com"), expected="success")
        customer.refresh_from_db()
        self.assertEqual(customer.full_name, "Corrected")
        self.assertEqual(customer.primary_email, "new@example.com")

    def test_deactivation_then_stale_update_cannot_resurrect(self):
        customer = create(self.company)
        self.run_concurrent(lambda: services.deactivate_customer(customer=customer),
                            lambda: services.update_customer(customer=customer, full_name="Corrected"), expected="success")
        customer.refresh_from_db()
        self.assertFalse(customer.is_active)
        self.assertEqual(customer.company_id, self.company.pk)
        self.assertEqual(customer.customer_number, "CUS-00000001")

    def test_company_deactivation_blocks_then_rejects_customer_reactivation(self):
        customer = services.deactivate_customer(customer=create(self.company))
        self.run_concurrent(lambda: organization_services.deactivate_company(company=self.company),
                            lambda: services.reactivate_customer(customer=customer), expected="validation")
        customer.refresh_from_db()
        self.assertFalse(customer.is_active)

    def test_admin_edit_waits_for_deactivation_and_preserves_inactive_state(self):
        customer = create(self.company)
        user = get_user_model().objects.create_superuser(username="race-customer-admin", password="test-only-password")
        def edit():
            client = Client()
            client.force_login(user)
            response = client.post(reverse("admin:customers_customer_change", args=[customer.pk]),
                {"full_name": "Corrected", "organization_name": "", "primary_mobile": "", "primary_email": "", "_save": "Save"})
            self.assertEqual(response.status_code, 302)
        self.run_concurrent(lambda: services.deactivate_customer(customer=customer), edit, expected="success")
        customer.refresh_from_db()
        self.assertFalse(customer.is_active)
        self.assertEqual(customer.full_name, "Corrected")
