import pytest
from unittest.mock import MagicMock, AsyncMock, patch
from datetime import datetime, timedelta

from app.ingest import PlaybackTracker, finalize_track


class TestPlaybackTracker:
    def test_initial_state(self):
        tracker = PlaybackTracker()
        assert tracker.current_track_id is None
        assert tracker.track_start_time is None
        assert tracker.accumulated_listen_ms == 0
        assert tracker.last_progress_ms == 0
        assert tracker.is_paused is False


class TestFinalizeTrack:
    def test_finalize_creates_play_history_when_not_exists(self):
        mock_db = MagicMock()
        mock_db.query.return_value.filter.return_value.first.return_value = None

        tracker = PlaybackTracker()
        tracker.current_track_id = "track123"
        tracker.track_start_time = datetime(2024, 1, 1, 10, 0, 0)
        tracker.accumulated_listen_ms = 60000

        finalize_track(mock_db, tracker)

        mock_db.add.assert_called_once()
        mock_db.commit.assert_called_once()

        assert tracker.current_track_id is None
        assert tracker.accumulated_listen_ms == 0

    def test_finalize_marks_skip_when_under_30s(self):
        mock_db = MagicMock()
        mock_db.query.return_value.filter.return_value.first.return_value = None

        tracker = PlaybackTracker()
        tracker.current_track_id = "track123"
        tracker.track_start_time = datetime(2024, 1, 1, 10, 0, 0)
        tracker.accumulated_listen_ms = 15000

        finalize_track(mock_db, tracker)

        call_args = mock_db.add.call_args[0][0]
        assert call_args.skipped is True

    def test_finalize_updates_existing_play(self):
        mock_existing = MagicMock()
        mock_existing.listened_ms = None

        mock_db = MagicMock()
        mock_db.query.return_value.filter.return_value.first.return_value = (
            mock_existing
        )

        tracker = PlaybackTracker()
        tracker.current_track_id = "track123"
        tracker.track_start_time = datetime(2024, 1, 1, 10, 0, 0)
        tracker.accumulated_listen_ms = 120000

        finalize_track(mock_db, tracker)

        assert mock_existing.listened_ms == 120000
        assert mock_existing.skipped is False
        mock_db.commit.assert_called_once()


class TestReccoBeatsClient:
    @pytest.mark.asyncio
    async def test_extracts_spotify_id_from_href(self):
        from app.services.reccobeats_client import ReccoBeatsClient

        with patch("httpx.AsyncClient") as mock_client:
            mock_response = MagicMock()
            mock_response.status_code = 200
            mock_response.json.return_value = {
                "content": [
                    {
                        "id": "uuid-here",
                        "href": "https://open.spotify.com/track/abc123xyz",
                        "energy": 0.8,
                        "valence": 0.6,
                    }
                ]
            }

            mock_client.return_value.__aenter__.return_value.get = AsyncMock(
                return_value=mock_response
            )

            client = ReccoBeatsClient()
            result = await client.get_audio_features(["abc123xyz"])

            assert len(result) == 1
            assert result[0]["spotify_id"] == "abc123xyz"
            assert result[0]["energy"] == 0.8

    @pytest.mark.asyncio
    async def test_returns_empty_on_error(self):
        from app.services.reccobeats_client import ReccoBeatsClient

        with patch("httpx.AsyncClient") as mock_client:
            mock_client.return_value.__aenter__.return_value.get = AsyncMock(
                side_effect=Exception("Network error")
            )

            client = ReccoBeatsClient()
            result = await client.get_audio_features(["test123"])

            assert result == []
