"""Ross1000 export orchestration service.

Implements the strategy pattern for two output modes:
- "xml"  → plain UTF-8 XML (default)
- "soap" → SOAP 1.1 envelope wrapping the XML payload

Architecture:
    Ross1000ExportService
        ├── XmlExportStrategy   → serialize_movement_days_xml()
        └── SoapEnvelopeStrategy → build_soap_envelope(xml_payload)

This service is the single entry point for the Ross1000 export pipeline.
It does NOT touch TXT export, C59, or Alloggiati pipelines.
"""

from __future__ import annotations

import logging
from abc import ABC, abstractmethod
from datetime import date

from istat.ross1000.exceptions import Ross1000ValidationError
from istat.ross1000.models.movement_payload import (
    Ross1000ExportResult,
    Ross1000MovementDayPayload,
)
from istat.ross1000.services.movement_builder import build_movement_days
from istat.ross1000.soap.envelope_builder import build_soap_envelope
from istat.ross1000.validators.payload_validator import validate_date_range, validate_structure
from istat.ross1000.xml.movement_serializer import serialize_movement_days_xml
from structures.models import Structure


logger = logging.getLogger(__name__)

ROSS1000_XML_CONTENT_TYPE = "application/xml; charset=utf-8"
ROSS1000_SOAP_CONTENT_TYPE = "text/xml; charset=utf-8"


# ---------------------------------------------------------------------------
# Strategy interfaces
# ---------------------------------------------------------------------------

class BaseMovementExportStrategy(ABC):
    """Abstract strategy for Ross1000 export serialization."""

    @abstractmethod
    def serialize(
        self,
        days: list[Ross1000MovementDayPayload],
        *,
        structure_code: str,
        start_date: date,
        end_date: date,
    ) -> tuple[str, str]:
        """Serialize movement days to (content, content_type)."""


class XmlExportStrategy(BaseMovementExportStrategy):
    """Serialize to plain UTF-8 XML."""

    def serialize(
        self,
        days: list[Ross1000MovementDayPayload],
        *,
        structure_code: str,
        start_date: date,
        end_date: date,
    ) -> tuple[str, str]:
        content = serialize_movement_days_xml(
            days,
            structure_code=structure_code,
            start_date=start_date,
            end_date=end_date,
        )
        return content, ROSS1000_XML_CONTENT_TYPE


class SoapEnvelopeStrategy(BaseMovementExportStrategy):
    """Serialize to SOAP 1.1 envelope wrapping the plain XML payload."""

    def __init__(self, *, username: str = "", password: str = "") -> None:
        self._username = username
        self._password = password

    def serialize(
        self,
        days: list[Ross1000MovementDayPayload],
        *,
        structure_code: str,
        start_date: date,
        end_date: date,
    ) -> tuple[str, str]:
        xml_payload = serialize_movement_days_xml(
            days,
            structure_code=structure_code,
            start_date=start_date,
            end_date=end_date,
        )
        soap_content = build_soap_envelope(
            xml_payload,
            username=self._username,
            password=self._password,
        )
        return soap_content, ROSS1000_SOAP_CONTENT_TYPE


# ---------------------------------------------------------------------------
# Base service interface (extensible for future regions)
# ---------------------------------------------------------------------------

class BaseTourismExportService(ABC):
    """Abstract base for regional tourism export services."""

    @abstractmethod
    def export(
        self,
        *,
        structure_id: int,
        start_date: date,
        end_date: date,
        mode: str,
    ) -> Ross1000ExportResult:
        """Generate an export result for the given parameters."""


# ---------------------------------------------------------------------------
# Ross1000 export service
# ---------------------------------------------------------------------------

class Ross1000ExportService(BaseTourismExportService):
    """Production-grade Ross1000 export service for Regione Liguria.

    Usage:
        service = Ross1000ExportService()
        result = service.export(
            structure_id=1,
            start_date=date(2026, 4, 1),
            end_date=date(2026, 4, 30),
            mode="xml",   # or "soap"
        )
    """

    def export(
        self,
        *,
        structure_id: int,
        start_date: date,
        end_date: date,
        mode: str = "xml",
        soap_username: str = "",
        soap_password: str = "",
    ) -> Ross1000ExportResult:
        """Generate a Ross1000 export for a structure and date range.

        Args:
            structure_id:   ID of the Structure to export
            start_date:     First day of the reporting period (inclusive)
            end_date:       Last day of the reporting period (inclusive)
            mode:           "xml" (default) or "soap"
            soap_username:  ISTAT credential username (SOAP mode only)
            soap_password:  ISTAT credential password (SOAP mode only)

        Returns:
            Ross1000ExportResult with filename, content, content_type, day_count, mode

        Raises:
            Ross1000ValidationError: On invalid inputs or missing data
        """
        valid_start, valid_end = validate_date_range(start_date, end_date)

        structure = Structure.objects.filter(id=structure_id).first()
        if structure is None:
            raise Ross1000ValidationError(f"Structure {structure_id} not found")

        validate_structure(structure)
        structure_code = str(structure.istat_code).strip()

        # Validate mode early — before any DB queries
        strategy = self._get_strategy(
            mode=mode,
            username=soap_username,
            password=soap_password,
        )

        # Build movement days (all DB queries happen here)
        days = build_movement_days(
            structure=structure,
            start_date=valid_start,
            end_date=valid_end,
        )

        content, content_type = strategy.serialize(
            days,
            structure_code=structure_code,
            start_date=valid_start,
            end_date=valid_end,
        )

        filename = _build_filename(
            structure_code=structure_code,
            start_date=valid_start,
            end_date=valid_end,
            mode=mode,
        )

        logger.info(
            "Ross1000 export generated",
            extra={
                "structure_id": structure_id,
                "structure_code": structure_code,
                "start_date": valid_start.isoformat(),
                "end_date": valid_end.isoformat(),
                "mode": mode,
                "day_count": len(days),
                "filename": filename,
                "status": "success",
            },
        )

        return Ross1000ExportResult(
            filename=filename,
            content=content,
            content_type=content_type,
            day_count=len(days),
            mode=mode,
        )

    @staticmethod
    def _get_strategy(
        mode: str,
        username: str,
        password: str,
    ) -> BaseMovementExportStrategy:
        if mode == "soap":
            return SoapEnvelopeStrategy(username=username, password=password)
        if mode == "xml":
            return XmlExportStrategy()
        raise Ross1000ValidationError(
            f"Unknown export mode '{mode}'. Valid modes: 'xml', 'soap'"
        )


def _build_filename(
    *,
    structure_code: str,
    start_date: date,
    end_date: date,
    mode: str,
) -> str:
    """Build a descriptive download filename."""
    safe_code = structure_code.replace("/", "-").replace(" ", "_")
    ext = "xml"
    return (
        f"Ross1000_{safe_code}_{start_date.strftime('%Y%m%d')}"
        f"_{end_date.strftime('%Y%m%d')}.{ext}"
    )
