"""Tests for Ross1000ExportService — strategy pattern and output modes."""

from __future__ import annotations

from datetime import date
from unittest.mock import MagicMock, patch

import pytest

from istat.ross1000.exceptions import Ross1000ValidationError
from istat.ross1000.models.movement_payload import (
    Ross1000ExportResult,
    Ross1000MovementDayPayload,
    Ross1000StrutturaPayload,
)
from istat.ross1000.services.export_service import (
    Ross1000ExportService,
    SoapEnvelopeStrategy,
    XmlExportStrategy,
    _build_filename,
)


# ---------------------------------------------------------------------------
# _build_filename()
# ---------------------------------------------------------------------------

class TestBuildFilename:
    def test_xml_extension(self):
        name = _build_filename(
            structure_code="TEST",
            start_date=date(2026, 4, 1),
            end_date=date(2026, 4, 30),
            mode="xml",
        )
        assert name.endswith(".xml")

    def test_contains_structure_code(self):
        name = _build_filename(
            structure_code="058091-CAV-00001",
            start_date=date(2026, 4, 1),
            end_date=date(2026, 4, 30),
            mode="xml",
        )
        assert "058091" in name

    def test_contains_dates(self):
        name = _build_filename(
            structure_code="TEST",
            start_date=date(2026, 4, 1),
            end_date=date(2026, 4, 30),
            mode="xml",
        )
        assert "20260401" in name
        assert "20260430" in name

    def test_slashes_in_code_replaced(self):
        name = _build_filename(
            structure_code="058/091",
            start_date=date(2026, 4, 1),
            end_date=date(2026, 4, 1),
            mode="xml",
        )
        assert "/" not in name


# ---------------------------------------------------------------------------
# XmlExportStrategy
# ---------------------------------------------------------------------------

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


class TestXmlExportStrategy:
    def test_returns_xml_content_type(self):
        strategy = XmlExportStrategy()
        days = [_make_empty_day(date(2026, 4, 1))]
        _, content_type = strategy.serialize(
            days,
            structure_code="TEST",
            start_date=date(2026, 4, 1),
            end_date=date(2026, 4, 1),
        )
        assert "application/xml" in content_type

    def test_returns_xml_string(self):
        strategy = XmlExportStrategy()
        days = [_make_empty_day(date(2026, 4, 1))]
        content, _ = strategy.serialize(
            days,
            structure_code="TEST",
            start_date=date(2026, 4, 1),
            end_date=date(2026, 4, 1),
        )
        assert "<?xml" in content
        assert "<movimenti" in content


# ---------------------------------------------------------------------------
# SoapEnvelopeStrategy
# ---------------------------------------------------------------------------

class TestSoapEnvelopeStrategy:
    def test_returns_text_xml_content_type(self):
        strategy = SoapEnvelopeStrategy()
        days = [_make_empty_day(date(2026, 4, 1))]
        _, content_type = strategy.serialize(
            days,
            structure_code="TEST",
            start_date=date(2026, 4, 1),
            end_date=date(2026, 4, 1),
        )
        assert "text/xml" in content_type

    def test_returns_soap_envelope(self):
        strategy = SoapEnvelopeStrategy()
        days = [_make_empty_day(date(2026, 4, 1))]
        content, _ = strategy.serialize(
            days,
            structure_code="TEST",
            start_date=date(2026, 4, 1),
            end_date=date(2026, 4, 1),
        )
        assert "Envelope" in content
        assert "Body" in content


# ---------------------------------------------------------------------------
# Ross1000ExportService
# ---------------------------------------------------------------------------

class TestRoss1000ExportService:

    @patch("istat.ross1000.services.export_service.Structure")
    @patch("istat.ross1000.services.export_service.build_movement_days")
    def test_returns_export_result(self, mock_build, mock_structure_cls):
        structure = MagicMock()
        structure.id = 1
        structure.istat_code = "058091-CAV-00001"
        mock_structure_cls.objects.filter.return_value.first.return_value = structure
        mock_build.return_value = [_make_empty_day(date(2026, 4, 1))]

        service = Ross1000ExportService()
        result = service.export(
            structure_id=1,
            start_date=date(2026, 4, 1),
            end_date=date(2026, 4, 1),
            mode="xml",
        )
        assert isinstance(result, Ross1000ExportResult)
        assert result.mode == "xml"
        assert result.day_count == 1

    @patch("istat.ross1000.services.export_service.Structure")
    @patch("istat.ross1000.services.export_service.build_movement_days")
    def test_soap_mode_returns_soap_result(self, mock_build, mock_structure_cls):
        structure = MagicMock()
        structure.id = 1
        structure.istat_code = "TEST"
        mock_structure_cls.objects.filter.return_value.first.return_value = structure
        mock_build.return_value = [_make_empty_day(date(2026, 4, 1))]

        service = Ross1000ExportService()
        result = service.export(
            structure_id=1,
            start_date=date(2026, 4, 1),
            end_date=date(2026, 4, 1),
            mode="soap",
        )
        assert result.mode == "soap"
        assert "Envelope" in result.content

    @patch("istat.ross1000.services.export_service.Structure")
    def test_raises_when_structure_not_found(self, mock_structure_cls):
        mock_structure_cls.objects.filter.return_value.first.return_value = None

        service = Ross1000ExportService()
        with pytest.raises(Ross1000ValidationError, match="not found"):
            service.export(
                structure_id=999,
                start_date=date(2026, 4, 1),
                end_date=date(2026, 4, 30),
            )

    @patch("istat.ross1000.services.export_service.Structure")
    def test_raises_for_invalid_mode(self, mock_structure_cls):
        structure = MagicMock()
        structure.id = 1
        structure.istat_code = "TEST"
        mock_structure_cls.objects.filter.return_value.first.return_value = structure

        service = Ross1000ExportService()
        with pytest.raises(Ross1000ValidationError, match="mode"):
            service.export(
                structure_id=1,
                start_date=date(2026, 4, 1),
                end_date=date(2026, 4, 30),
                mode="invalid",
            )

    @patch("istat.ross1000.services.export_service.Structure")
    def test_raises_when_end_before_start(self, mock_structure_cls):
        service = Ross1000ExportService()
        with pytest.raises(Ross1000ValidationError):
            service.export(
                structure_id=1,
                start_date=date(2026, 4, 30),
                end_date=date(2026, 4, 1),
            )

    @patch("istat.ross1000.services.export_service.Structure")
    @patch("istat.ross1000.services.export_service.build_movement_days")
    def test_filename_contains_structure_code(self, mock_build, mock_structure_cls):
        structure = MagicMock()
        structure.id = 1
        structure.istat_code = "058091-CAV-00001"
        mock_structure_cls.objects.filter.return_value.first.return_value = structure
        mock_build.return_value = []

        service = Ross1000ExportService()
        result = service.export(
            structure_id=1,
            start_date=date(2026, 4, 1),
            end_date=date(2026, 4, 30),
        )
        assert "058091" in result.filename

    @patch("istat.ross1000.services.export_service.Structure")
    @patch("istat.ross1000.services.export_service.build_movement_days")
    def test_day_count_matches_movement_days(self, mock_build, mock_structure_cls):
        structure = MagicMock()
        structure.id = 1
        structure.istat_code = "TEST"
        mock_structure_cls.objects.filter.return_value.first.return_value = structure
        mock_build.return_value = [
            _make_empty_day(date(2026, 4, 1) + __import__("datetime").timedelta(days=i))
            for i in range(30)
        ]

        service = Ross1000ExportService()
        result = service.export(
            structure_id=1,
            start_date=date(2026, 4, 1),
            end_date=date(2026, 4, 30),
        )
        assert result.day_count == 30
