"""
Integration tests for health data sync flow.

Tests the complete sync pipeline with mocked Garmin API,
verifying storage and repository layers are correctly updated.
"""

import pytest
from datetime import date
from pathlib import Path
from unittest.mock import MagicMock, patch


@pytest.fixture
def isolated_health_dir(tmp_path: Path) -> Path:
    """Create an isolated data directory for each test."""
    data_dir = tmp_path / "health"
    data_dir.mkdir()
    return data_dir


@pytest.fixture
def mock_repo() -> MagicMock:
    """Mock HealthRepository that reports no pre-existing data."""
    repo = MagicMock()
    repo.get_daily_metric_path.return_value = None  # no indexed path
    repo.index_daily_metric.return_value = None
    repo.get_daily_metric_record.return_value = None
    return repo


class TestManualLogFlow:
    """Test manual logging write + read roundtrip."""

    def test_diet_log_roundtrip(self, isolated_health_dir: Path) -> None:
        """Log a diet entry and read it back correctly."""
        from health.services.manual_log_storage import ManualLogStorage
        from health.models.manual_log import DietEntry

        storage = ManualLogStorage(data_dir=isolated_health_dir)
        today = date.today()

        entry = DietEntry(time="12:30", description="鸡胸肉 + 糙米", meal_type="lunch")
        storage.add_diet_entry(today, entry)

        log = storage.load_log(today)
        assert len(log.diet_entries) == 1
        assert log.diet_entries[0].description == "鸡胸肉 + 糙米"
        assert log.diet_entries[0].meal_type == "lunch"

    def test_multiple_log_types_same_day(self, isolated_health_dir: Path) -> None:
        """Multiple log types written on the same day all persist."""
        from health.services.manual_log_storage import ManualLogStorage
        from health.models.manual_log import DietEntry, AlcoholEntry, SupplementEntry

        storage = ManualLogStorage(data_dir=isolated_health_dir)
        today = date.today()

        storage.add_diet_entry(today, DietEntry(time="08:00", description="燕麦", meal_type="breakfast"))
        storage.add_alcohol_entry(today, AlcoholEntry(time="19:00", drink_type="红酒", amount="200ml"))
        storage.add_supplement_entry(today, SupplementEntry(time="07:00", supplement_name="镁", dosage="400mg"))

        log = storage.load_log(today)
        assert len(log.diet_entries) == 1
        assert len(log.alcohol_entries) == 1
        assert len(log.supplement_entries) == 1

    def test_appending_multiple_entries(self, isolated_health_dir: Path) -> None:
        """Multiple diet entries on the same day all append correctly."""
        from health.services.manual_log_storage import ManualLogStorage
        from health.models.manual_log import DietEntry

        storage = ManualLogStorage(data_dir=isolated_health_dir)
        today = date.today()

        for meal, meal_type in [("早餐", "breakfast"), ("午餐", "lunch"), ("晚餐", "dinner")]:
            storage.add_diet_entry(today, DietEntry(time="09:00", description=meal, meal_type=meal_type))

        log = storage.load_log(today)
        assert len(log.diet_entries) == 3
        descriptions = [e.description for e in log.diet_entries]
        assert "早餐" in descriptions
        assert "晚餐" in descriptions

    def test_load_nonexistent_date_returns_empty_log(self, isolated_health_dir: Path) -> None:
        """Loading logs for a date with no data returns an empty DailyManualLog."""
        from health.services.manual_log_storage import ManualLogStorage

        storage = ManualLogStorage(data_dir=isolated_health_dir)
        log = storage.load_log(date(2020, 1, 1))
        assert len(log.diet_entries) == 0
        assert len(log.alcohol_entries) == 0


class TestHealthStorage:
    """Test JSON-based daily metric storage (with mocked repo)."""

    def test_save_and_load_metric(self, isolated_health_dir: Path, mock_repo: MagicMock) -> None:
        """Save a metric and load it back with correct content."""
        from health.services.storage import HealthStorage
        from health.models.daily_metrics import StepsData

        with patch("health.services.storage.HealthRepository", return_value=mock_repo):
            storage = HealthStorage(data_dir=isolated_health_dir)

        today = date.today()
        steps = StepsData(date=today, total_steps=8000, total_distance_meters=6400.0)
        storage.save_daily_metric(steps, "steps")

        loaded = storage.load_daily_metric("steps", today)
        assert loaded is not None
        assert loaded["total_steps"] == 8000

    def test_metric_not_indexed_before_save(self, isolated_health_dir: Path, mock_repo: MagicMock) -> None:
        """metric_exists returns False when no index entry and no file exists."""
        from health.services.storage import HealthStorage

        with patch("health.services.storage.HealthRepository", return_value=mock_repo):
            storage = HealthStorage(data_dir=isolated_health_dir)

        # repo returns None → no indexed path → file doesn't exist → False
        assert not storage.metric_exists("sleep", date.today())

    def test_metric_exists_after_save(self, isolated_health_dir: Path, mock_repo: MagicMock) -> None:
        """metric_exists returns True after a metric is saved."""
        from health.services.storage import HealthStorage
        from health.models.daily_metrics import StepsData

        today = date.today()

        with patch("health.services.storage.HealthRepository", return_value=mock_repo):
            storage = HealthStorage(data_dir=isolated_health_dir)

        steps = StepsData(date=today, total_steps=5000)
        storage.save_daily_metric(steps, "steps")

        # After save, update mock_repo to return the file path
        import health.config as hconfig
        storage_path = hconfig.DATA_TYPE_CONFIG["steps"]["storage_path"]
        year, month = str(today.year), f"{today.month:02d}"
        expected_path = isolated_health_dir / storage_path / year / month / f"{today.isoformat()}.json"
        mock_repo.get_daily_metric_path.return_value = str(expected_path)

        assert storage.metric_exists("steps", today)

    def test_overwrite_metric(self, isolated_health_dir: Path, mock_repo: MagicMock) -> None:
        """Saving the same metric twice overwrites the previous data."""
        from health.services.storage import HealthStorage
        from health.models.daily_metrics import StepsData

        with patch("health.services.storage.HealthRepository", return_value=mock_repo):
            storage = HealthStorage(data_dir=isolated_health_dir)

        today = date.today()
        storage.save_daily_metric(StepsData(date=today, total_steps=3000), "steps")
        storage.save_daily_metric(StepsData(date=today, total_steps=9999), "steps")

        loaded = storage.load_daily_metric("steps", today)
        assert loaded["total_steps"] == 9999


class TestHealthDataSyncWithMocks:
    """Test HealthDataSync orchestration with mocked Garmin client."""

    def _make_sync(self, isolated_health_dir: Path, mock_repo: MagicMock) -> tuple:
        """Helper to create sync service with mocked dependencies."""
        from health.services.storage import HealthStorage
        from health.services.data_sync import HealthDataSync

        with patch("health.services.storage.HealthRepository", return_value=mock_repo):
            storage = HealthStorage(data_dir=isolated_health_dir)

        mock_garmin = MagicMock()
        sync = HealthDataSync(client=mock_garmin, storage=storage, repo=mock_repo)
        return sync, storage, mock_garmin

    def test_sync_skips_existing_data(self, isolated_health_dir: Path, mock_repo: MagicMock) -> None:
        """sync_daily_metric skips already-synced dates when force=False."""
        from health.models.daily_metrics import StepsData

        sync, storage, mock_garmin = self._make_sync(isolated_health_dir, mock_repo)
        today = date.today()

        # Pre-populate so file exists
        storage.save_daily_metric(StepsData(date=today, total_steps=5000), "steps")

        # Update mock to return the file path (simulating indexed entry)
        import health.config as hconfig
        storage_path = hconfig.DATA_TYPE_CONFIG["steps"]["storage_path"]
        year, month = str(today.year), f"{today.month:02d}"
        file_path = isolated_health_dir / storage_path / year / month / f"{today.isoformat()}.json"
        mock_repo.get_daily_metric_path.return_value = str(file_path)

        result = sync.sync_daily_metric("steps", today, force=False)

        assert result is False
        mock_garmin.fetch_steps.assert_not_called()

    def test_sync_forces_overwrite(self, isolated_health_dir: Path, mock_repo: MagicMock) -> None:
        """sync_daily_metric re-fetches and saves new data when force=True."""
        from health.models.daily_metrics import StepsData

        sync, storage, mock_garmin = self._make_sync(isolated_health_dir, mock_repo)
        today = date.today()

        # Configure mock to return new steps data
        new_steps = StepsData(date=today, total_steps=12000, total_distance_meters=9600.0)
        mock_garmin.fetch_steps.return_value = new_steps

        result = sync.sync_daily_metric("steps", today, force=True)

        assert result is True
        mock_garmin.fetch_steps.assert_called_once_with(today)
        loaded = storage.load_daily_metric("steps", today)
        assert loaded is not None
        assert loaded["total_steps"] == 12000
