"""
Unit tests for Garmin Lifestyle Logging feature.

Tests the parsing logic in GarminHealthClient.fetch_lifestyle_logging()
against realistic mock API responses based on actual Garmin API output.
"""

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

import pytest

from health.models.daily_metrics import LifestyleLoggingData
from health.services.garmin_client import GarminHealthClient


# --- Fixtures: realistic mock API responses ---

MOCK_RESPONSE_WITH_LOGS = {
    "dailyLogsReport": [
        {
            "behaviourId": 1,
            "measurementType": "QUANTITY",
            "calendarDate": "2026-03-01",
            "name": "Alcohol",
            "category": "LIFESTYLE",
            "details": [
                {"subTypeId": 2, "subTypeName": "WINE", "amount": 2},
                {"subTypeId": 1, "subTypeName": "BEER"},  # no amount = not logged
            ],
            "logStatus": "YES",
            "sleepRelated": False,
        },
        {
            "behaviourId": 2,
            "measurementType": "QUANTITY",
            "calendarDate": "2026-03-01",
            "name": "Morning Caffeine",
            "logStatus": "YES",
            "category": "LIFESTYLE",
            "details": [
                {"subTypeId": 1, "subTypeName": "COFFEE", "amount": 1},
            ],
            "sleepRelated": False,
        },
        {
            "behaviourId": 3,
            "measurementType": "NONE",
            "calendarDate": "2026-03-01",
            "name": "Light Exercise",
            "logStatus": "YES",
            "category": "LIFESTYLE",
            "sleepRelated": False,
        },
        {
            "behaviourId": 4,
            "measurementType": "NONE",
            "calendarDate": "2026-03-01",
            "name": "Healthy Meals",
            "logStatus": "YES",
            "category": "LIFESTYLE",
            "sleepRelated": False,
        },
        # Not logged — no logStatus field
        {
            "behaviourId": 5,
            "measurementType": "NONE",
            "calendarDate": "2026-03-01",
            "name": "Heavy Meals",
            "category": "LIFESTYLE",
            "sleepRelated": False,
        },
        {
            "behaviourId": 6,
            "measurementType": "NONE",
            "calendarDate": "2026-03-01",
            "name": "Late Meals",
            "category": "LIFESTYLE",
            "sleepRelated": False,
        },
        {
            "behaviourId": 42,
            "measurementType": "QUANTITY",
            "calendarDate": "2026-03-01",
            "name": "Late Caffeine",
            "category": "LIFESTYLE",
            "details": [
                {"subTypeId": 1, "subTypeName": "COFFEE"},
            ],
            "sleepRelated": False,
        },
        {
            "behaviourId": 8,
            "measurementType": "NONE",
            "calendarDate": "2026-03-01",
            "name": "Intermittent Fasting",
            "category": "LIFESTYLE",
            "sleepRelated": False,
        },
    ],
    "completionStats": [
        {"calendarDate": "2026-03-01", "totalTracking": 8, "completedTracking": 3}
    ],
}

MOCK_RESPONSE_EMPTY_LOGS = {
    "dailyLogsReport": [
        {
            "behaviourId": 1,
            "measurementType": "QUANTITY",
            "calendarDate": "2026-03-02",
            "name": "Alcohol",
            "category": "LIFESTYLE",
            "details": [{"subTypeId": 1, "subTypeName": "BEER"}],
            "sleepRelated": False,
        },
    ],
    "completionStats": [
        {"calendarDate": "2026-03-02", "totalTracking": 8, "completedTracking": 0}
    ],
}


@pytest.fixture
def client() -> GarminHealthClient:
    """Return a GarminHealthClient with auth mocked out."""
    with patch.object(GarminHealthClient, "__init__", lambda self, **kw: None):
        c = GarminHealthClient.__new__(GarminHealthClient)
        c.client = MagicMock()
        c._authenticated = True
        return c


class TestFetchLifestyleLogging:
    """Tests for fetch_lifestyle_logging parsing logic."""

    def test_parses_alcohol_quantity(self, client: GarminHealthClient) -> None:
        """Alcohol with logStatus=YES and WINE amount=2 should parse correctly."""
        with patch.object(client, "_retry_api_call", return_value=MOCK_RESPONSE_WITH_LOGS):
            with patch.object(client, "_ensure_authenticated"):
                result = client.fetch_lifestyle_logging(date(2026, 3, 1))

        assert result is not None
        assert result.alcohol_logged is True
        assert result.alcohol_wine == 2
        assert result.alcohol_beer == 0  # no amount field
        assert result.alcohol_spirit == 0
        assert result.alcohol_other == 0

    def test_parses_morning_caffeine(self, client: GarminHealthClient) -> None:
        """Morning caffeine with coffee=1 should parse correctly."""
        with patch.object(client, "_retry_api_call", return_value=MOCK_RESPONSE_WITH_LOGS):
            with patch.object(client, "_ensure_authenticated"):
                result = client.fetch_lifestyle_logging(date(2026, 3, 1))

        assert result is not None
        assert result.morning_caffeine_logged is True
        assert result.morning_caffeine_coffee == 1
        assert result.morning_caffeine_tea == 0

    def test_parses_boolean_behaviors(self, client: GarminHealthClient) -> None:
        """NONE-type behaviors with logStatus=YES should be True; others False."""
        with patch.object(client, "_retry_api_call", return_value=MOCK_RESPONSE_WITH_LOGS):
            with patch.object(client, "_ensure_authenticated"):
                result = client.fetch_lifestyle_logging(date(2026, 3, 1))

        assert result is not None
        assert result.light_exercise is True
        assert result.healthy_meals is True
        assert result.heavy_meals is False      # no logStatus
        assert result.late_meals is False       # no logStatus
        assert result.intermittent_fasting is False  # no logStatus

    def test_late_caffeine_not_logged(self, client: GarminHealthClient) -> None:
        """Late Caffeine without logStatus=YES should not be logged."""
        with patch.object(client, "_retry_api_call", return_value=MOCK_RESPONSE_WITH_LOGS):
            with patch.object(client, "_ensure_authenticated"):
                result = client.fetch_lifestyle_logging(date(2026, 3, 1))

        assert result is not None
        assert result.late_caffeine_logged is False
        assert result.late_caffeine_coffee == 0

    def test_returns_correct_date(self, client: GarminHealthClient) -> None:
        """Result date must match requested date."""
        with patch.object(client, "_retry_api_call", return_value=MOCK_RESPONSE_WITH_LOGS):
            with patch.object(client, "_ensure_authenticated"):
                result = client.fetch_lifestyle_logging(date(2026, 3, 1))

        assert result is not None
        assert result.date == date(2026, 3, 1)

    def test_returns_none_when_no_logs(self, client: GarminHealthClient) -> None:
        """Day with no logged behaviors (no logStatus=YES anywhere) returns None."""
        with patch.object(client, "_retry_api_call", return_value=MOCK_RESPONSE_EMPTY_LOGS):
            with patch.object(client, "_ensure_authenticated"):
                result = client.fetch_lifestyle_logging(date(2026, 3, 2))

        assert result is None

    def test_returns_none_on_empty_api_response(self, client: GarminHealthClient) -> None:
        """None API response should return None."""
        with patch.object(client, "_retry_api_call", return_value=None):
            with patch.object(client, "_ensure_authenticated"):
                result = client.fetch_lifestyle_logging(date(2026, 3, 2))

        assert result is None

    def test_raw_data_preserved(self, client: GarminHealthClient) -> None:
        """raw_data field must contain the full API response."""
        with patch.object(client, "_retry_api_call", return_value=MOCK_RESPONSE_WITH_LOGS):
            with patch.object(client, "_ensure_authenticated"):
                result = client.fetch_lifestyle_logging(date(2026, 3, 1))

        assert result is not None
        assert result.raw_data == MOCK_RESPONSE_WITH_LOGS
