"""Tests for Ross1000 SOAP payload builder and schemas.

Validates:
- build_ross1000_soap_payload() produces a valid SOAP envelope
- The inner XML payload is embedded correctly
- Credentials are passed through to the WS-Security header
- Schema constants are correct and consistent
"""

from __future__ import annotations

from datetime import date
from xml.etree import ElementTree

import pytest

from istat.ross1000.models.movement_payload import (
    Ross1000MovementDayPayload,
    Ross1000StrutturaPayload,
)
from istat.ross1000.soap.payload_builder import build_ross1000_soap_payload
from istat.ross1000.soap.schemas import (
    OPERATION_INVIA_MOVIMENTI,
    ROSS1000_ENDPOINT_URL,
    ROSS1000_NS,
    ROSS1000_WSDL_URL,
    SOAP_ACTION_INVIA_MOVIMENTI,
    SOAP_CONTENT_TYPE,
    SOAP_ENV_NS,
    XML_CONTENT_TYPE,
)


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

def _make_empty_day(day: date) -> Ross1000MovementDayPayload:
    struttura = Ross1000StrutturaPayload(
        codice="058091-CAV-00001",
        apertura=1,
        camere_occupate=0,
        camere_disponibili=6,
        letti_disponibili=20,
    )
    return Ross1000MovementDayPayload(date=day, struttura=struttura)


def _parse(xml_str: str) -> ElementTree.Element:
    return ElementTree.fromstring(xml_str.encode("utf-8"))


# ---------------------------------------------------------------------------
# build_ross1000_soap_payload()
# ---------------------------------------------------------------------------

class TestBuildRoss1000SoapPayload:

    def test_returns_string(self):
        days = [_make_empty_day(date(2026, 4, 1))]
        result = build_ross1000_soap_payload(
            days,
            structure_code="058091-CAV-00001",
            start_date=date(2026, 4, 1),
            end_date=date(2026, 4, 1),
        )
        assert isinstance(result, str)

    def test_xml_declaration_present(self):
        days = [_make_empty_day(date(2026, 4, 1))]
        result = build_ross1000_soap_payload(
            days,
            structure_code="TEST",
            start_date=date(2026, 4, 1),
            end_date=date(2026, 4, 1),
        )
        assert result.startswith("<?xml")

    def test_utf8_encoding_declared(self):
        days = [_make_empty_day(date(2026, 4, 1))]
        result = build_ross1000_soap_payload(
            days,
            structure_code="TEST",
            start_date=date(2026, 4, 1),
            end_date=date(2026, 4, 1),
        )
        assert "UTF-8" in result or "utf-8" in result.lower()

    def test_root_is_soap_envelope(self):
        days = [_make_empty_day(date(2026, 4, 1))]
        result = build_ross1000_soap_payload(
            days,
            structure_code="TEST",
            start_date=date(2026, 4, 1),
            end_date=date(2026, 4, 1),
        )
        root = _parse(result)
        assert "Envelope" in root.tag
        assert SOAP_ENV_NS in root.tag

    def test_soap_body_contains_operation(self):
        days = [_make_empty_day(date(2026, 4, 1))]
        result = build_ross1000_soap_payload(
            days,
            structure_code="TEST",
            start_date=date(2026, 4, 1),
            end_date=date(2026, 4, 1),
            operation="inviaMovimenti",
        )
        root = _parse(result)
        body = root.find(f"{{{SOAP_ENV_NS}}}Body")
        assert body is not None
        operation = body.find(f"{{{ROSS1000_NS}}}inviaMovimenti")
        assert operation is not None

    def test_inner_xml_payload_embedded(self):
        days = [_make_empty_day(date(2026, 4, 1))]
        result = build_ross1000_soap_payload(
            days,
            structure_code="058091-CAV-00001",
            start_date=date(2026, 4, 1),
            end_date=date(2026, 4, 1),
        )
        # The inner movimenti XML must be present inside the envelope
        assert "movimenti" in result
        assert "058091-CAV-00001" in result

    def test_no_wssecurity_without_credentials(self):
        days = [_make_empty_day(date(2026, 4, 1))]
        result = build_ross1000_soap_payload(
            days,
            structure_code="TEST",
            start_date=date(2026, 4, 1),
            end_date=date(2026, 4, 1),
        )
        root = _parse(result)
        header = root.find(f"{{{SOAP_ENV_NS}}}Header")
        assert header is not None
        assert len(list(header)) == 0

    def test_wssecurity_added_with_credentials(self):
        days = [_make_empty_day(date(2026, 4, 1))]
        result = build_ross1000_soap_payload(
            days,
            structure_code="TEST",
            start_date=date(2026, 4, 1),
            end_date=date(2026, 4, 1),
            username="istat_user",
            password="istat_pass",
        )
        root = _parse(result)
        header = root.find(f"{{{SOAP_ENV_NS}}}Header")
        assert len(list(header)) > 0

    def test_empty_days_list_still_produces_valid_soap(self):
        result = build_ross1000_soap_payload(
            [],
            structure_code="TEST",
            start_date=date(2026, 4, 1),
            end_date=date(2026, 4, 30),
        )
        root = _parse(result)
        assert "Envelope" in root.tag

    def test_30_day_range_embedded_in_soap(self):
        days = [
            _make_empty_day(date(2026, 4, 1) + __import__("datetime").timedelta(days=i))
            for i in range(30)
        ]
        result = build_ross1000_soap_payload(
            days,
            structure_code="TEST",
            start_date=date(2026, 4, 1),
            end_date=date(2026, 4, 30),
        )
        # All 30 movimento blocks must be present inside the SOAP envelope
        assert result.count('data="2026') == 30 or result.count("movimento") >= 30

    def test_custom_operation_name(self):
        days = [_make_empty_day(date(2026, 4, 1))]
        result = build_ross1000_soap_payload(
            days,
            structure_code="TEST",
            start_date=date(2026, 4, 1),
            end_date=date(2026, 4, 1),
            operation="customOp",
        )
        root = _parse(result)
        body = root.find(f"{{{SOAP_ENV_NS}}}Body")
        operation = body.find(f"{{{ROSS1000_NS}}}customOp")
        assert operation is not None


# ---------------------------------------------------------------------------
# schemas.py constants
# ---------------------------------------------------------------------------

class TestSoapSchemas:

    def test_soap_env_ns_is_soap11(self):
        assert SOAP_ENV_NS == "http://schemas.xmlsoap.org/soap/envelope/"

    def test_ross1000_ns_contains_liguria(self):
        assert "liguria" in ROSS1000_NS.lower()

    def test_endpoint_url_is_https(self):
        assert ROSS1000_ENDPOINT_URL.startswith("https://")

    def test_wsdl_url_ends_with_wsdl(self):
        assert ROSS1000_WSDL_URL.endswith("?wsdl")

    def test_operation_name_is_invia_movimenti(self):
        assert OPERATION_INVIA_MOVIMENTI == "inviaMovimenti"

    def test_soap_action_contains_operation(self):
        assert OPERATION_INVIA_MOVIMENTI in SOAP_ACTION_INVIA_MOVIMENTI

    def test_soap_content_type_is_text_xml(self):
        assert "text/xml" in SOAP_CONTENT_TYPE

    def test_xml_content_type_is_application_xml(self):
        assert "application/xml" in XML_CONTENT_TYPE

    def test_both_content_types_declare_utf8(self):
        assert "utf-8" in SOAP_CONTENT_TYPE.lower()
        assert "utf-8" in XML_CONTENT_TYPE.lower()
