"""Tests for Ross1000 structure resolver.

Validates:
- struttura payload is built correctly from a structure instance
- istat_code is used as codice
- apertura is always 1
- camere_occupate is 0 (overridden per-day by movement_builder)
- raises Ross1000StructureError when istat_code is missing
- available rooms and beds fall back to Property queries when total_units is 0
"""

from __future__ import annotations

from unittest.mock import MagicMock, patch

import pytest

from istat.ross1000.exceptions import Ross1000StructureError
from istat.ross1000.models.movement_payload import Ross1000StrutturaPayload
from istat.ross1000.services.structure_resolver import (
    _available_beds,
    _available_rooms,
    build_struttura_payload,
)

_RESOLVER = "istat.ross1000.services.structure_resolver"


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

def _make_structure(istat_code="058091-CAV-00001", total_units=6):
    structure = MagicMock()
    structure.id = 1
    structure.istat_code = istat_code
    structure.total_units = total_units
    structure.total_rooms = None   # prevent MagicMock fallback in _available_rooms
    return structure


# ---------------------------------------------------------------------------
# build_struttura_payload()
# Patch _available_rooms and _available_beds so no real DB queries fire.
# ---------------------------------------------------------------------------

class TestBuildStrutturaPayload:

    @patch(f"{_RESOLVER}._available_beds", return_value=20)
    @patch(f"{_RESOLVER}._available_rooms", return_value=6)
    def test_returns_struttura_payload(self, _r, _b):
        result = build_struttura_payload(structure=_make_structure())
        assert isinstance(result, Ross1000StrutturaPayload)

    @patch(f"{_RESOLVER}._available_beds", return_value=20)
    @patch(f"{_RESOLVER}._available_rooms", return_value=6)
    def test_codice_matches_istat_code(self, _r, _b):
        result = build_struttura_payload(structure=_make_structure(istat_code="058091-CAV-00001"))
        assert result.codice == "058091-CAV-00001"

    @patch(f"{_RESOLVER}._available_beds", return_value=20)
    @patch(f"{_RESOLVER}._available_rooms", return_value=6)
    def test_apertura_is_always_1(self, _r, _b):
        result = build_struttura_payload(structure=_make_structure())
        assert result.apertura == 1

    @patch(f"{_RESOLVER}._available_beds", return_value=20)
    @patch(f"{_RESOLVER}._available_rooms", return_value=6)
    def test_camere_occupate_is_zero(self, _r, _b):
        """camere_occupate is always 0 here — overridden per-day by movement_builder."""
        result = build_struttura_payload(structure=_make_structure())
        assert result.camere_occupate == 0

    def test_raises_when_istat_code_missing(self):
        with pytest.raises(Ross1000StructureError, match="istat_code"):
            build_struttura_payload(structure=_make_structure(istat_code=""))

    def test_raises_when_istat_code_is_none(self):
        with pytest.raises(Ross1000StructureError):
            build_struttura_payload(structure=_make_structure(istat_code=None))

    def test_raises_when_istat_code_is_whitespace(self):
        with pytest.raises(Ross1000StructureError):
            build_struttura_payload(structure=_make_structure(istat_code="   "))

    @patch(f"{_RESOLVER}._available_beds", return_value=20)
    @patch(f"{_RESOLVER}._available_rooms", return_value=6)
    def test_payload_is_frozen(self, _r, _b):
        result = build_struttura_payload(structure=_make_structure())
        with pytest.raises((AttributeError, TypeError)):
            result.codice = "changed"  # type: ignore

    @patch(f"{_RESOLVER}._available_beds", return_value=20)
    @patch(f"{_RESOLVER}._available_rooms", return_value=10)
    def test_camere_disponibili_comes_from_available_rooms(self, _r, _b):
        result = build_struttura_payload(structure=_make_structure())
        assert result.camere_disponibili == 10

    @patch(f"{_RESOLVER}._available_beds", return_value=24)
    @patch(f"{_RESOLVER}._available_rooms", return_value=6)
    def test_letti_disponibili_comes_from_available_beds(self, _r, _b):
        result = build_struttura_payload(structure=_make_structure())
        assert result.letti_disponibili == 24


# ---------------------------------------------------------------------------
# _available_rooms() — unit tests
# ---------------------------------------------------------------------------

class TestAvailableRooms:

    def test_returns_total_units_when_positive(self):
        assert _available_rooms(_make_structure(total_units=8)) == 8

    @patch(f"{_RESOLVER}.Property")
    def test_falls_back_to_property_count_when_zero(self, mock_property):
        mock_property.objects.filter.return_value.count.return_value = 3
        mock_property.Availability.AVAILABLE = "available"
        assert _available_rooms(_make_structure(total_units=0)) == 3

    @patch(f"{_RESOLVER}.Property")
    def test_falls_back_to_property_count_when_none(self, mock_property):
        mock_property.objects.filter.return_value.count.return_value = 5
        mock_property.Availability.AVAILABLE = "available"
        assert _available_rooms(_make_structure(total_units=None)) == 5


# ---------------------------------------------------------------------------
# _available_beds() — unit tests
# ---------------------------------------------------------------------------

class TestAvailableBeds:

    @patch(f"{_RESOLVER}.Property")
    def test_returns_sum_of_beds_and_sofa_beds(self, mock_property):
        mock_qs = MagicMock()
        mock_qs.annotate.return_value.aggregate.return_value = {"total": 20}
        mock_property.objects.filter.return_value = mock_qs
        mock_property.Availability.AVAILABLE = "available"
        assert _available_beds(_make_structure()) == 20

    @patch(f"{_RESOLVER}.Property")
    def test_returns_zero_when_no_properties(self, mock_property):
        mock_qs = MagicMock()
        mock_qs.annotate.return_value.aggregate.return_value = {"total": 0}
        mock_property.objects.filter.return_value = mock_qs
        mock_property.Availability.AVAILABLE = "available"
        assert _available_beds(_make_structure()) == 0
