from __future__ import annotations

import tempfile
import unittest
from datetime import date
from pathlib import Path

from django.contrib.auth import get_user_model
from django.test import TestCase
from rest_framework.test import APIClient

from guests.models import Guest
from bookings.models import Booking
from istat.models import IstatMunicipality
from properties.models import Property, PropertyType
from structures.models import Structure, StructureUser


MINIMAL_C59_XSD = """<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:rim="http://www.regione.liguria.it/turismo/rimovcli"
    targetNamespace="http://www.regione.liguria.it/turismo/rimovcli"
    elementFormDefault="qualified">
  <xs:element name="c59">
    <xs:complexType>
      <xs:sequence>
        <xs:element name="mensile" minOccurs="1" maxOccurs="1"/>
        <xs:element name="giornaliero" minOccurs="1" maxOccurs="1"/>
      </xs:sequence>
      <xs:attribute name="idstruttura" type="xs:string" use="required"/>
      <xs:attribute name="data" type="xs:string" use="required"/>
    </xs:complexType>
  </xs:element>
</xs:schema>
"""


LXML_AVAILABLE = True
try:
    import lxml.etree  # noqa: F401
except ModuleNotFoundError:
    LXML_AVAILABLE = False


User = get_user_model()


class XmlExportApiTests(TestCase):
    def setUp(self):
        self.client = APIClient()
        self.owner = User.objects.create_user(username="owner", password="pass123")
        self.other_user = User.objects.create_user(username="other", password="pass123")
        self.member = User.objects.create_user(username="member", password="pass123")

        self.structure = Structure.objects.create(
            user=self.owner,
            name="Hotel Liguria",
            structure_type="Hotel",
            zip_code="16121",
            country="Italy",
            istat_code="LIG001",
        )
        StructureUser.objects.create(
            structure=self.structure,
            user=self.member,
            role="Viewer",
            created_by=self.owner,
        )
        self.property_type = PropertyType.objects.create(
            structure=self.structure,
            name="Double Room",
            max_guests=2,
            num_beds=2,
        )
        self.property = Property.objects.create(
            structure=self.structure,
            property_type=self.property_type,
            name="Room 101",
        )
        IstatMunicipality.objects.create(
            code="015146000",
            name="Milano",
            province="MI",
            region="Lombardia",
        )

    def _create_booking_with_guest(self, *, adults_count=1):
        booking = Booking.objects.create(
            structure=self.structure,
            property_type=self.property_type,
            property=self.property,
            check_in_date=date(2026, 5, 13),
            check_out_date=date(2026, 5, 14),
            adults_count=adults_count,
            children_count=0,
            tourism_type="Business",
            transport_type="Car",
            is_checked_in=True,
        )
        Guest.objects.create(
            booking=booking,
            full_name="Mario Rossi",
            is_main_guest=True,
            guest_type="16",
            gender="male",
            date_of_birth=date(1990, 1, 1),
            country_of_birth="IT",
            nationality="IT",
            country="IT",
            city="Milano",
            extra_data={"province": "MI"},
        )
        return booking

    def test_guest_export_authenticated_success(self):
        self._create_booking_with_guest()
        self.client.force_authenticate(self.owner)

        response = self.client.get(
            "/api/istat/xml/guest-export/",
            {
                "structure_id": self.structure.id,
                "start_date": "2026-05-13",
                "end_date": "2026-05-13",
            },
        )

        self.assertEqual(response.status_code, 200)
        self.assertEqual(response["Content-Type"], "application/xml")
        self.assertIn("attachment;", response["Content-Disposition"])
        self.assertIn("istat_movimenti_", response["Content-Disposition"])

    def test_guest_export_unauthenticated_rejected(self):
        response = self.client.get(
            "/api/istat/xml/guest-export/",
            {
                "structure_id": self.structure.id,
                "start_date": "2026-05-13",
                "end_date": "2026-05-13",
            },
        )

        self.assertIn(response.status_code, (401, 403))

    def test_guest_export_invalid_dates_rejected(self):
        self.client.force_authenticate(self.owner)
        response = self.client.get(
            "/api/istat/xml/guest-export/",
            {
                "structure_id": self.structure.id,
                "start_date": "bad-date",
                "end_date": "2026-05-13",
            },
        )

        self.assertEqual(response.status_code, 400)

    def test_guest_export_end_date_before_start_date_rejected(self):
        self.client.force_authenticate(self.owner)
        response = self.client.get(
            "/api/istat/xml/guest-export/",
            {
                "structure_id": self.structure.id,
                "start_date": "2026-05-14",
                "end_date": "2026-05-13",
            },
        )

        self.assertEqual(response.status_code, 400)

    def test_guest_export_missing_structure_rejected(self):
        self.client.force_authenticate(self.owner)
        response = self.client.get(
            "/api/istat/xml/guest-export/",
            {
                "structure_id": 999999,
                "start_date": "2026-05-13",
                "end_date": "2026-05-13",
            },
        )

        self.assertEqual(response.status_code, 404)

    def test_guest_export_permission_denied(self):
        self._create_booking_with_guest()
        self.client.force_authenticate(self.other_user)
        response = self.client.get(
            "/api/istat/xml/guest-export/",
            {
                "structure_id": self.structure.id,
                "start_date": "2026-05-13",
                "end_date": "2026-05-13",
            },
        )

        self.assertEqual(response.status_code, 403)

    def test_guest_export_structure_member_allowed(self):
        self._create_booking_with_guest()
        self.client.force_authenticate(self.member)
        response = self.client.get(
            "/api/istat/xml/guest-export/",
            {
                "structure_id": self.structure.id,
                "start_date": "2026-05-13",
                "end_date": "2026-05-13",
            },
        )

        self.assertEqual(response.status_code, 200)

    def test_guest_export_synthetic_guest_validation_returns_clean_json(self):
        self._create_booking_with_guest(adults_count=2)
        self.client.force_authenticate(self.owner)
        response = self.client.get(
            "/api/istat/xml/guest-export/",
            {
                "structure_id": self.structure.id,
                "start_date": "2026-05-13",
                "end_date": "2026-05-13",
            },
        )

        self.assertEqual(response.status_code, 400)
        self.assertIn("detail", response.json())
        self.assertNotIn("Traceback", response.content.decode("utf-8"))

    def test_c59_export_authenticated_success(self):
        self._create_booking_with_guest()
        self.client.force_authenticate(self.owner)
        response = self.client.get(
            "/api/istat/xml/c59-export/",
            {
                "structure_id": self.structure.id,
                "report_date": "2026-05-13",
            },
        )

        self.assertEqual(response.status_code, 200)
        self.assertEqual(response["Content-Type"], "application/xml")
        self.assertIn("istat_c59_", response["Content-Disposition"])

    @unittest.skipUnless(LXML_AVAILABLE, "lxml is not installed")
    def test_c59_export_xsd_validation_enabled(self):
        self._create_booking_with_guest()
        self.client.force_authenticate(self.owner)
        with tempfile.NamedTemporaryFile("w", suffix=".xsd", delete=False) as handle:
            handle.write(MINIMAL_C59_XSD)
            xsd_path = handle.name

        self.addCleanup(lambda: Path(xsd_path).unlink(missing_ok=True))
        response = self.client.get(
            "/api/istat/xml/c59-export/",
            {
                "structure_id": self.structure.id,
                "report_date": "2026-05-13",
                "validate_xsd": "true",
                "xsd_path": xsd_path,
            },
        )

        self.assertEqual(response.status_code, 200)

    def test_c59_export_missing_istat_code_failure(self):
        self._create_booking_with_guest()
        self.structure.istat_code = ""
        self.structure.save(update_fields=["istat_code"])
        self.client.force_authenticate(self.owner)
        response = self.client.get(
            "/api/istat/xml/c59-export/",
            {
                "structure_id": self.structure.id,
                "report_date": "2026-05-13",
            },
        )

        self.assertEqual(response.status_code, 400)
        self.assertIn("detail", response.json())

    def test_c59_export_permission_denied(self):
        self._create_booking_with_guest()
        self.client.force_authenticate(self.other_user)
        response = self.client.get(
            "/api/istat/xml/c59-export/",
            {
                "structure_id": self.structure.id,
                "report_date": "2026-05-13",
            },
        )

        self.assertEqual(response.status_code, 403)
