import uuid
from unittest.mock import patch
from datetime import timedelta

from django.contrib import admin
from django.contrib.auth import get_user_model
from django.core.exceptions import ValidationError
from django.db import IntegrityError, transaction
from django.db.models.deletion import ProtectedError
from django.test import RequestFactory, TestCase
from django.urls import reverse

from .models import Company, Department, Region, ServiceCenter


class OrganizationTests(TestCase):
    @classmethod
    def setUpTestData(cls):
        cls.company = Company.objects.create(code="ccare", name="c-care")
        cls.other = Company.objects.create(code="other", name="Other")
        cls.region = Region.objects.create(company=cls.company, code="dhaka", name="Dhaka")
        cls.other_region = Region.objects.create(company=cls.other, code="dhaka", name="Dhaka")

    def center(self, **overrides):
        values = dict(company=self.company, region=self.region, code="dhk01",
                      name="Dhaka Service Center", center_type=ServiceCenter.CenterType.OWN)
        values.update(overrides)
        return ServiceCenter.objects.create(**values)

    def assert_database_rejects(self, operation):
        with self.assertRaises(IntegrityError), transaction.atomic():
            operation()

    def test_company_creation_defaults(self):
        self.assertIsInstance(self.company.pk, uuid.UUID)
        self.assertEqual(self.company.code, "CCARE")
        self.assertTrue(self.company.is_active)
        self.assertEqual(self.company.legal_name, "")
        self.assertIsNotNone(self.company.created_at)
        self.assertIsNotNone(self.company.updated_at)

    def test_all_models_uuid_timestamps_and_defaults(self):
        department = Department.objects.create(company=self.company, code="service", name="Service")
        for obj in [self.company, self.region, self.center(), department]:
            with self.subTest(model=type(obj).__name__):
                obj.refresh_from_db()
                self.assertIsInstance(obj.pk, uuid.UUID)
                self.assertTrue(obj.is_active)
                self.assertIsNotNone(obj.created_at)
                self.assertIsNotNone(obj.updated_at)

    def test_timestamps_updated_on_partial_save(self):
        created, updated = self.company.created_at, self.company.updated_at
        self.company.name = "New display name"
        with patch("django.utils.timezone.now", return_value=updated + timedelta(seconds=1)):
            self.company.save(update_fields=["name"])
        self.company.refresh_from_db()
        self.assertEqual(self.company.created_at, created)
        self.assertGreater(self.company.updated_at, updated)

    def test_codes_normalize_without_mutating_text(self):
        company = Company.objects.create(code="  new-co_1  ", name="  Display Name  ", legal_name="  Legal  ")
        region = Region.objects.create(company=company, code=" north ", name=" North ")
        department = Department.objects.create(company=company, code=" care ", name=" Care ")
        center = self.center(company=company, region=region, code=" n01 ", address="  Address\n ")
        for obj, code in [(company, "NEW-CO_1"), (region, "NORTH"), (department, "CARE"), (center, "N01")]:
            self.assertEqual(obj.code, code)
        self.assertEqual(company.name, "  Display Name  ")
        self.assertEqual(company.legal_name, "  Legal  ")
        self.assertEqual(center.address, "  Address\n ")

    def test_company_duplicate_and_case_equivalent_rejected(self):
        for code in ["CCARE", "ccare", " CcArE "]:
            with self.subTest(code=code), self.assertRaises(ValidationError):
                Company.objects.create(code=code, name="Duplicate")

    def test_company_database_uniqueness(self):
        self.assert_database_rejects(lambda: Company.objects.bulk_create([Company(code="CCARE", name="Duplicate")]))

    def test_database_rejects_noncanonical_codes_for_all_models(self):
        center = self.center()
        department = Department.objects.create(company=self.company, code="service", name="Service")
        for obj in [self.company, self.region, center, department]:
            for code in ["lower", " CCARE ", "", "HAS SPACE"]:
                with self.subTest(model=type(obj).__name__, code=code):
                    self.assert_database_rejects(lambda: type(obj).objects.filter(pk=obj.pk).update(code=code))

    def test_required_and_invalid_codes_rejected(self):
        for code in ["", "   ", "BAD CODE", "!INVALID", "A" * 33]:
            with self.subTest(code=code), self.assertRaises(ValidationError):
                Company.objects.create(code=code, name="Invalid")
        with self.assertRaises(ValidationError):
            Company.objects.create(code="VALID", name="")

    def test_region_relationship_and_scoped_codes(self):
        self.assertEqual(self.region.company, self.company)
        self.assertEqual(self.region.code, "DHAKA")
        self.assertEqual(self.other_region.code, self.region.code)
        self.assertNotEqual(self.other_region.company, self.region.company)

    def test_region_duplicate_rejected(self):
        with self.assertRaises(ValidationError):
            Region.objects.create(company=self.company, code=" dhaka ", name="Duplicate")
        self.assert_database_rejects(lambda: Region.objects.bulk_create([
            Region(company=self.company, code="DHAKA", name="Duplicate")]))

    def test_region_protects_company_deletion(self):
        with self.assertRaises(ProtectedError):
            self.company.delete()

    def test_service_center_creation(self):
        center = self.center()
        self.assertEqual(center.company, self.company)
        self.assertEqual(center.region, self.region)
        self.assertEqual(center.code, "DHK01")
        self.assertEqual(center.phone, "")
        self.assertEqual(center.email, "")
        self.assertEqual(center.address, "")

    def test_service_center_choices(self):
        self.assertEqual(ServiceCenter.CenterType.labels, [
            "Own Service Center", "Authorized Service Center", "Partner Service Center"])
        for value in ServiceCenter.CenterType.values:
            self.assertEqual(self.center(code=value, center_type=value).center_type, value)
        with self.assertRaises(ValidationError):
            self.center(code="INVALID", center_type="INVALID")
        self.assert_database_rejects(lambda: ServiceCenter.objects.update(center_type="INVALID"))

    def test_service_center_email_validation(self):
        with self.assertRaises(ValidationError):
            self.center(email="invalid-email")
        self.assertEqual(self.center(email="center@example.com").email, "center@example.com")

    def test_service_center_code_unique_across_regions_within_company(self):
        self.center()
        region = Region.objects.create(company=self.company, code="SOUTH", name="South")
        with self.assertRaises(ValidationError):
            self.center(region=region, code=" dhk01 ")
        self.assert_database_rejects(lambda: ServiceCenter.objects.bulk_create([
            ServiceCenter(company=self.company, region=region, code="DHK01", name="Duplicate", center_type="OWN")]))

    def test_service_center_code_allowed_in_another_company(self):
        first = self.center()
        other = self.center(company=self.other, region=self.other_region)
        self.assertEqual(first.code, other.code)

    def test_service_center_clean_rejects_mismatch(self):
        center = ServiceCenter(company=self.other, region=self.region, code="BAD", name="Bad", center_type="OWN")
        with self.assertRaisesMessage(ValidationError, "Region must belong"):
            center.full_clean()

    def test_service_center_create_rejects_mismatch(self):
        with self.assertRaises(ValidationError):
            self.center(company=self.other)
        self.assertFalse(ServiceCenter.objects.exists())

    def test_service_center_save_rejects_mismatch(self):
        center = self.center()
        center.company = self.other
        with self.assertRaises(ValidationError):
            center.save()
        center.refresh_from_db()
        self.assertEqual(center.company, self.company)

    def test_service_center_partial_relationship_save_preserves_company(self):
        center = self.center()
        destination = Region.objects.create(company=self.company, code="NORTH", name="North")
        center.region = destination
        center.save(update_fields=["region"])
        center.refresh_from_db()
        self.assertEqual(center.company, self.company)
        self.assertEqual(center.region, destination)
        center.company, center.region = self.other, self.other_region
        with self.assertRaises(ValidationError):
            center.save(update_fields=["company", "region"])

    def test_service_center_protects_region_and_company(self):
        self.center()
        for obj in [self.region, self.company]:
            with self.subTest(model=type(obj).__name__), self.assertRaises(ProtectedError):
                obj.delete()

    def test_region_move_with_centers_rejected(self):
        self.center()
        self.region.company = self.other
        with self.assertRaises(ValidationError):
            self.region.full_clean()
        with self.assertRaises(ValidationError):
            self.region.save(update_fields=["company"])
        self.region.refresh_from_db()
        self.assertEqual(self.region.company, self.company)

    def test_empty_region_cannot_change_company(self):
        region = Region.objects.create(company=self.company, code="EMPTY", name="Empty")
        region.company = self.other
        with self.assertRaises(ValidationError):
            region.save()
        region.refresh_from_db()
        self.assertEqual(region.company, self.company)

    def test_stale_region_cache_cannot_bypass_validation(self):
        region = Region.objects.create(company=self.company, code="EMPTY", name="Empty")
        center = ServiceCenter(company=self.company, region=region, code="NEW", name="New", center_type="OWN")
        # Simulate an unsupported bulk write to verify the relation cache is ignored.
        Region.objects.filter(pk=region.pk).update(company=self.other)
        with self.assertRaises(ValidationError):
            center.save()

    def test_department_creation_and_scoped_uniqueness(self):
        department = Department.objects.create(company=self.company, code=" service ", name="Service")
        self.assertEqual(department.code, "SERVICE")
        self.assertEqual(department.company, self.company)
        Department.objects.create(company=self.other, code="service", name="Service")
        with self.assertRaises(ValidationError):
            Department.objects.create(company=self.company, code="service", name="Duplicate")
        self.assert_database_rejects(lambda: Department.objects.bulk_create([
            Department(company=self.company, code="SERVICE", name="Duplicate")]))

    def test_department_protects_company(self):
        company = Company.objects.create(code="DEPTCO", name="Department only")
        Department.objects.create(company=company, code="CARE", name="Care")
        with self.assertRaises(ProtectedError):
            company.delete()

    def test_unreferenced_records_can_be_deleted(self):
        company = Company.objects.create(code="TEMP", name="Temporary")
        pk = company.pk
        company.delete()
        self.assertFalse(Company.objects.filter(pk=pk).exists())

    def test_inactive_is_not_deleted_and_keeps_code_reserved(self):
        from .services import deactivate_company
        deactivate_company(company=self.company)
        self.assertTrue(Company.objects.filter(pk=self.company.pk, is_active=False).exists())
        with self.assertRaises(ValidationError):
            Company.objects.create(code="CCARE", name="Duplicate")

    def test_representations_do_not_query_database(self):
        department = Department.objects.create(company=self.company, code="SERVICE", name="Service")
        center = self.center()
        for obj in [self.company, self.region, center, department]:
            with self.assertNumQueries(0):
                self.assertEqual(str(obj), f"{obj.code} ? {obj.name}")


class OrganizationAdminTests(TestCase):
    @classmethod
    def setUpTestData(cls):
        cls.user = get_user_model().objects.create_superuser(username="org-admin", password="test-only-password")
        cls.company = Company.objects.create(code="CCARE", name="c-care")
        cls.other = Company.objects.create(code="OTHER", name="Other")
        cls.region = Region.objects.create(company=cls.company, code="DHAKA", name="Dhaka")

    def test_all_models_registered(self):
        for model in [Company, Region, ServiceCenter, Department]:
            self.assertIn(model, admin.site._registry)

    def test_admin_lists_load(self):
        self.client.force_login(self.user)
        for model in [Company, Region, ServiceCenter, Department]:
            with self.subTest(model=model.__name__):
                response = self.client.get(reverse(f"admin:organization_{model._meta.model_name}_changelist"))
                self.assertEqual(response.status_code, 200)

    def test_admin_form_rejects_mismatch(self):
        request = RequestFactory().get("/admin/")
        request.user = self.user
        form_class = admin.site._registry[ServiceCenter].get_form(request)
        form = form_class(data={"company": self.other.pk, "region": self.region.pk,
                               "code": "DHK01", "name": "Dhaka", "center_type": "OWN", "is_active": True})
        self.assertFalse(form.is_valid())
        self.assertIn("region", form.errors)

    def test_admin_creates_normalized_record(self):
        self.client.force_login(self.user)
        response = self.client.post(reverse("admin:organization_servicecenter_add"), {
            "company": self.company.pk, "region": self.region.pk, "code": " dhk01 ",
            "name": "Dhaka", "center_type": "OWN", "is_active": "on", "_save": "Save",
        })
        self.assertEqual(response.status_code, 302)
        self.assertEqual(ServiceCenter.objects.get().code, "DHK01")
