from types import SimpleNamespace

from django.contrib.auth.models import User
from django.test import SimpleTestCase, TestCase

from .istat_utils import apply_guest_istat_defaults, derive_guest_type_codes
from .guest_defaults import (
    ISTAT_NOT_SPECIFIED,
    normalize_guest_for_istat,
    normalize_istat_guest_defaults,
)
from .models import Guest
from .serializers import CheckInGuestSerializer, GuestSerializer
from .services import sync_booking_guests
from bookings.models import Booking
from istat.models import IstatCountry, IstatMunicipality
from properties.models import Property, PropertyType
from services.country_utils import normalize_country, resolve_istat_country_code
from structures.models import Structure


class GuestIstatUtilsTests(SimpleTestCase):
    def test_derive_guest_type_codes(self):
        self.assertEqual(derive_guest_type_codes("single", 2), ["16", "16"])
        self.assertEqual(derive_guest_type_codes("family", 3), ["17", "19", "19"])
        self.assertEqual(derive_guest_type_codes("group", 2), ["18", "20"])

    def test_apply_guest_istat_defaults_promotes_extra_data_and_uses_booking_fallbacks(self):
        booking = SimpleNamespace(
            guest_group_type="family",
            tourism_type="Beach",
            transport_type="Train",
        )
        payload = {
            "full_name": "John Doe",
            "extra_data": {
                "guest_type": "16",
                "tourism_type": "Cultural",
                "transport_type": "Car",
                "custom_field": "kept",
            },
        }

        enriched = apply_guest_istat_defaults(
            payload,
            booking=booking,
            index=0,
            total_guests=2,
        )

        self.assertEqual(enriched["guest_type"], "16")
        self.assertEqual(enriched["tourism_type"], "Cultural")
        self.assertEqual(enriched["transport_type"], "Car")
        self.assertEqual(enriched["extra_data"], {"custom_field": "kept"})

    def test_apply_guest_istat_defaults_fills_missing_values_from_booking(self):
        booking = SimpleNamespace(
            guest_group_type="group",
            tourism_type="Conference/Business",
            transport_type="Bus",
        )
        payload = {
            "full_name": "Jane Doe",
            "extra_data": {},
        }

        enriched = apply_guest_istat_defaults(
            payload,
            booking=booking,
            index=1,
            total_guests=2,
        )

        self.assertEqual(enriched["guest_type"], "20")
        self.assertEqual(enriched["tourism_type"], "Conference/Business")
        self.assertEqual(enriched["transport_type"], "Bus")


class GuestIstatDefaultTests(SimpleTestCase):
    def test_normalizes_null_empty_and_whitespace_values(self):
        normalized = normalize_istat_guest_defaults(
            {
                "country_of_birth": "IT",
                "nationality": None,
                "country": "   ",
                "tourism_type": "",
                "transport_type": None,
            }
        )

        self.assertEqual(normalized["nationality"], "IT")
        self.assertEqual(normalized["country"], "IT")
        self.assertEqual(normalized["tourism_type"], ISTAT_NOT_SPECIFIED)
        self.assertEqual(normalized["transport_type"], ISTAT_NOT_SPECIFIED)

    def test_country_falls_back_to_nationality_when_birth_country_missing(self):
        normalized = normalize_istat_guest_defaults(
            {
                "country_of_birth": None,
                "nationality": "IN",
                "country": "",
            }
        )

        self.assertEqual(normalized["country"], "IN")

    def test_preserves_valid_values(self):
        normalized = normalize_istat_guest_defaults(
            {
                "country_of_birth": "IT",
                "nationality": "US",
                "country": "FR",
                "tourism_type": "Cultural",
                "transport_type": "Train",
            }
        )

        self.assertEqual(normalized["nationality"], "US")
        self.assertEqual(normalized["country"], "FR")
        self.assertEqual(normalized["tourism_type"], "Cultural")
        self.assertEqual(normalized["transport_type"], "Train")

    def test_preserves_existing_values_from_extra_data(self):
        normalized = normalize_istat_guest_defaults(
            {
                "country_of_birth": "IT",
                "nationality": None,
                "country": None,
                "tourism_type": None,
                "transport_type": None,
            },
            extra_data={
                "nationality": "US",
                "country": "FR",
                "tourism_type": "Beach",
                "transport_type": "Train",
            },
        )

        self.assertEqual(normalized["nationality"], "US")
        self.assertEqual(normalized["country"], "FR")
        self.assertEqual(normalized["tourism_type"], "Beach")
        self.assertEqual(normalized["transport_type"], "Train")

    def test_export_normalization_does_not_mutate_original_guest(self):
        guest = SimpleNamespace(
            country_of_birth="IN",
            nationality=None,
            country=None,
            tourism_type=None,
            transport_type=" ",
            extra_data={},
        )

        normalized = normalize_guest_for_istat(guest)

        self.assertIsNone(guest.nationality)
        self.assertIsNone(guest.country)
        self.assertEqual(guest.transport_type, " ")
        self.assertEqual(normalized.nationality, "IN")
        self.assertEqual(normalized.country, "IN")
        self.assertEqual(normalized.transport_type, ISTAT_NOT_SPECIFIED)


class GuestProvinceValidationTests(TestCase):
    @classmethod
    def setUpTestData(cls):
        IstatMunicipality.objects.create(
            code="037006000",
            name="Bologna",
            province="BO",
            region="Emilia-Romagna",
        )

    def test_checkin_guest_serializer_requires_province_for_italian_resident(self):
        serializer = CheckInGuestSerializer(
            data={
                "full_name": "Italian Guest",
                "is_main_guest": True,
                "country": "IT",
            }
        )

        self.assertFalse(serializer.is_valid())
        self.assertIn("province", serializer.errors)
        self.assertIn("city", serializer.errors)
        self.assertEqual(
            str(serializer.errors["province"][0]),
            "Province is required for Italian residents.",
        )

    def test_checkin_guest_serializer_allows_foreign_resident_without_province(self):
        serializer = CheckInGuestSerializer(
            data={
                "full_name": "Foreign Guest",
                "is_main_guest": True,
                "country": "IN",
            }
        )

        self.assertTrue(serializer.is_valid(), serializer.errors)

    def test_guest_serializer_requires_province_for_italian_resident(self):
        serializer = GuestSerializer(
            data={
                "full_name": "Italian Guest",
                "country": "IT",
                "extra_data": {},
            }
        )

        self.assertFalse(serializer.is_valid())
        self.assertIn("province", serializer.errors)
        self.assertIn("city", serializer.errors)
        self.assertEqual(
            str(serializer.errors["province"][0]),
            "Province is required for Italian residents.",
        )

    def test_checkin_guest_serializer_accepts_valid_italian_municipality(self):
        serializer = CheckInGuestSerializer(
            data={
                "full_name": "Italian Guest",
                "is_main_guest": True,
                "country": "IT",
                "city": "Bologna",
                "province": "BO",
            }
        )

        self.assertTrue(serializer.is_valid(), serializer.errors)

    def test_checkin_guest_serializer_rejects_invalid_italian_municipality(self):
        serializer = CheckInGuestSerializer(
            data={
                "full_name": "Italian Guest",
                "is_main_guest": True,
                "country": "IT",
                "city": "Thane",
                "province": "BO",
            }
        )

        self.assertFalse(serializer.is_valid())
        self.assertIn("city", serializer.errors)

    def test_checkin_guest_serializer_rejects_mismatched_municipality_province(self):
        serializer = CheckInGuestSerializer(
            data={
                "full_name": "Italian Guest",
                "is_main_guest": True,
                "country": "IT",
                "city": "Bologna",
                "province": "AL",
            }
        )

        self.assertFalse(serializer.is_valid())
        self.assertIn("province", serializer.errors)

    def test_guest_serializer_loads_old_italian_guest_without_province(self):
        guest = Guest(
            full_name="Old Italian Guest",
            country="IT",
            extra_data={},
        )

        data = GuestSerializer(guest).data

        self.assertEqual(data["country"], "IT")
        self.assertNotIn("province", data)

    def test_checkin_guest_serializer_returns_province_when_present(self):
        guest = Guest(
            full_name="Italian Guest",
            country="IT",
            extra_data={"province": "MI"},
        )

        data = CheckInGuestSerializer(guest).data

        self.assertEqual(data["province"], "MI")


class CountryNormalizationTests(TestCase):
    def setUp(self):
        self.user = User.objects.create_user(username="guest-country-owner")
        self.structure = Structure.objects.create(
            user=self.user,
            name="Country Test Structure",
            structure_type="Hotel",
            country="IT",
            zip_code="16121",
        )
        self.property_type = PropertyType.objects.create(
            structure=self.structure,
            name="Room",
            max_guests=2,
            num_beds=1,
        )
        self.property = Property.objects.create(
            structure=self.structure,
            property_type=self.property_type,
            name="Room 1",
        )
        self.booking = Booking.objects.create(
            structure=self.structure,
            property_type=self.property_type,
            property=self.property,
            check_in_date="2026-04-01",
            check_out_date="2026-04-03",
            adults_count=1,
            children_count=0,
        )
        IstatCountry.objects.create(code="000000100", name="Italy", iso_code="IT")
        IstatCountry.objects.create(code="000000380", name="India", iso_code="IN")
        IstatCountry.objects.create(
            code="000000840",
            name="United States",
            iso_code="US",
        )

    def test_normalize_country_returns_iso2_for_names_and_iso3(self):
        self.assertEqual(normalize_country("Italy"), "IT")
        self.assertEqual(normalize_country("IND"), "IN")
        self.assertEqual(normalize_country("United States"), "US")

    def test_resolve_istat_country_code_uses_iso2_storage_value(self):
        self.assertEqual(resolve_istat_country_code("IT"), "000000100")
        self.assertEqual(resolve_istat_country_code("India"), "000000380")

    def test_sync_booking_guests_normalizes_country_fields(self):
        synced_guests = sync_booking_guests(
            booking=self.booking,
            guests_data=[
                {
                    "full_name": "Guest One",
                    "is_main_guest": True,
                    "country_of_birth": "Italy",
                    "document_issuing_country": "USA",
                    "nationality": "IND",
                    "country": "United States",
                    "extra_data": {
                        "country_of_birth": "Italy",
                        "nationality": "IND",
                        "country": "United States",
                    },
                }
            ],
        )

        guest = synced_guests[0]
        self.assertEqual(guest.country_of_birth, "IT")
        self.assertEqual(guest.document_issuing_country, "US")
        self.assertEqual(guest.nationality, "IN")
        self.assertEqual(guest.country, "US")
        self.assertEqual(guest.extra_data["country_of_birth"], "IT")
        self.assertEqual(guest.extra_data["nationality"], "IN")
        self.assertEqual(guest.extra_data["country"], "US")

    def test_guest_save_applies_istat_defaults(self):
        guest = Guest.objects.create(
            booking=self.booking,
            full_name="Fallback Guest",
            is_main_guest=True,
            country_of_birth="IN",
            nationality=" ",
            country=None,
            tourism_type="",
            transport_type=None,
            extra_data={},
        )

        self.assertEqual(guest.nationality, "IN")
        self.assertEqual(guest.country, "IN")
        self.assertEqual(guest.tourism_type, ISTAT_NOT_SPECIFIED)
        self.assertEqual(guest.transport_type, ISTAT_NOT_SPECIFIED)
