from __future__ import annotations

import tempfile
import unittest
from pathlib import Path
from unittest.mock import patch

from django.test import SimpleTestCase

from istat.xml_export.exceptions import XmlPayloadValidationError
from istat.xml_export.validation.xsd_validator import (
    load_default_c59_xsd_path,
    validate_c59_xml_against_xsd,
)


VALID_XSD = """<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="root">
    <xs:complexType>
      <xs:sequence>
        <xs:element name="item" type="xs:string"/>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
</xs:schema>
"""


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


class XsdValidatorTests(SimpleTestCase):
    def test_load_default_c59_xsd_path_returns_module_local_path(self):
        self.assertTrue(load_default_c59_xsd_path().endswith("validation/schemas/c59.xsd"))

    @unittest.skipUnless(LXML_AVAILABLE, "lxml is not installed")
    def test_valid_xml_passes_xsd_validation(self):
        xml_content = "<root><item>ok</item></root>"
        with tempfile.NamedTemporaryFile("w", suffix=".xsd", delete=False) as handle:
            handle.write(VALID_XSD)
            xsd_path = handle.name

        self.addCleanup(lambda: Path(xsd_path).unlink(missing_ok=True))
        validate_c59_xml_against_xsd(xml_content, xsd_path)

    @unittest.skipUnless(LXML_AVAILABLE, "lxml is not installed")
    def test_invalid_xml_fails_xsd_validation(self):
        xml_content = "<root><wrong>no</wrong></root>"
        with tempfile.NamedTemporaryFile("w", suffix=".xsd", delete=False) as handle:
            handle.write(VALID_XSD)
            xsd_path = handle.name

        self.addCleanup(lambda: Path(xsd_path).unlink(missing_ok=True))
        with self.assertRaises(XmlPayloadValidationError) as context:
            validate_c59_xml_against_xsd(xml_content, xsd_path)

        self.assertIn("failed XSD validation", str(context.exception))

    def test_missing_xsd_fails(self):
        with self.assertRaises(XmlPayloadValidationError) as context:
            validate_c59_xml_against_xsd("<root/>", "/tmp/does-not-exist-c59.xsd")

        self.assertIn("XSD schema file not found", str(context.exception))

    @unittest.skipUnless(LXML_AVAILABLE, "lxml is not installed")
    def test_malformed_xml_fails(self):
        with tempfile.NamedTemporaryFile("w", suffix=".xsd", delete=False) as handle:
            handle.write(VALID_XSD)
            xsd_path = handle.name

        self.addCleanup(lambda: Path(xsd_path).unlink(missing_ok=True))
        with self.assertRaises(XmlPayloadValidationError) as context:
            validate_c59_xml_against_xsd("<root>", xsd_path)

        self.assertIn("Malformed XML content", str(context.exception))

    def test_missing_lxml_fails_clearly(self):
        with tempfile.NamedTemporaryFile("w", suffix=".xsd", delete=False) as handle:
            handle.write(VALID_XSD)
            xsd_path = handle.name

        self.addCleanup(lambda: Path(xsd_path).unlink(missing_ok=True))
        with patch(
            "istat.xml_export.validation.xsd_validator.importlib.import_module",
            side_effect=ModuleNotFoundError("No module named 'lxml'"),
        ):
            with self.assertRaises(XmlPayloadValidationError) as context:
                validate_c59_xml_against_xsd("<root><item>ok</item></root>", xsd_path)

        self.assertIn("requires the optional 'lxml' dependency", str(context.exception))
