"""Tests for MediaService."""

import asyncio
import json
import tempfile
from pathlib import Path
from unittest.mock import AsyncMock, MagicMock, patch

import pytest
from tenacity import RetryError

from src.services.media_service import (
    MediaService,
    MediaServiceProtocol,
    create_media_service,
)


class TestMediaService:
    """Test cases for MediaService."""

    @pytest.fixture
    def media_service(self):
        """Create a MediaService instance for testing."""
        return MediaService()

    @pytest.fixture
    def temp_dir(self):
        """Create a temporary directory for testing."""
        with tempfile.TemporaryDirectory() as temp_dir:
            yield Path(temp_dir)

    @pytest.mark.asyncio
    async def test_media_service_initialization(self, media_service):
        """Test MediaService initialization."""
        # Mock FFmpeg check to avoid actual system dependency
        with patch("asyncio.create_subprocess_exec") as mock_subprocess:
            mock_process = AsyncMock()
            mock_process.communicate.return_value = (b"", b"")
            mock_process.returncode = 0
            mock_subprocess.return_value = mock_process

            await media_service.initialize()
            assert media_service.status.value == "healthy"

    @pytest.mark.asyncio
    async def test_media_service_initialization_ffmpeg_not_found(self, media_service):
        """Test MediaService initialization when FFmpeg is not found."""
        with patch("asyncio.create_subprocess_exec") as mock_subprocess:
            mock_subprocess.side_effect = FileNotFoundError("ffmpeg not found")

            with pytest.raises(RuntimeError, match="FFmpeg not found"):
                await media_service.initialize()

    @pytest.mark.asyncio
    async def test_validate_file_size(self, media_service, temp_dir):
        """Test file size validation."""
        # Create a test file
        test_file = temp_dir / "test.txt"
        test_file.write_text("test content")

        # Test valid file size
        is_valid = await media_service.validate_file_size(test_file, max_size_mb=1)
        assert is_valid is True

        # Test invalid file size (file doesn't exist)
        is_valid = await media_service.validate_file_size(temp_dir / "nonexistent.txt")
        assert is_valid is False

    @pytest.mark.asyncio
    async def test_check_audio_quality(self, media_service, temp_dir):
        """Test audio quality checking."""
        # Mock FFprobe to return valid duration
        with patch("asyncio.create_subprocess_exec") as mock_subprocess:
            mock_process = AsyncMock()
            mock_process.communicate.return_value = (b"10.5", b"")  # 10.5 seconds
            mock_process.returncode = 0
            mock_subprocess.return_value = mock_process

            test_file = temp_dir / "test.wav"
            test_file.write_text("fake audio content")

            is_valid = await media_service.check_audio_quality(test_file)
            assert is_valid is True

    @pytest.mark.asyncio
    async def test_check_audio_quality_too_short(self, media_service, temp_dir):
        """Test audio quality checking for files that are too short."""
        with patch("asyncio.create_subprocess_exec") as mock_subprocess:
            mock_process = AsyncMock()
            mock_process.communicate.return_value = (b"0.05", b"")  # 0.05 seconds (too short)
            mock_process.returncode = 0
            mock_subprocess.return_value = mock_process

            test_file = temp_dir / "test.wav"
            test_file.write_text("fake audio content")

            is_valid = await media_service.check_audio_quality(test_file)
            assert is_valid is False

    @pytest.mark.asyncio
    async def test_get_media_info(self, media_service, temp_dir):
        """Test getting media information."""
        # Mock FFprobe to return JSON info
        mock_json_response = {
            "format": {
                "duration": "120.5",
                "format_name": "mp3",
                "bit_rate": "128000",
                "size": "1920000"
            },
            "streams": [
                {
                    "codec_type": "audio",
                    "codec_name": "mp3",
                    "sample_rate": "44100",
                    "channels": 2
                }
            ]
        }

        with patch("asyncio.create_subprocess_exec") as mock_subprocess:
            mock_process = AsyncMock()
            mock_process.communicate.return_value = (
                json.dumps(mock_json_response).encode(),
                b""
            )
            mock_process.returncode = 0
            mock_subprocess.return_value = mock_process

            test_file = temp_dir / "test.mp3"
            test_file.write_text("fake audio content")

            info = await media_service.get_media_info(test_file)
            assert "duration" in info
            assert "mime_type" in info

    @pytest.mark.asyncio
    async def test_preprocess_audio(self, media_service, temp_dir):
        """Test audio preprocessing."""
        input_file = temp_dir / "input.mp3"
        output_file = temp_dir / "output.wav"
        input_file.write_text("fake input audio")

        # Mock FFmpeg and FFprobe
        with patch("asyncio.create_subprocess_exec") as mock_subprocess:
            # Mock FFmpeg conversion
            mock_ffmpeg = AsyncMock()
            mock_ffmpeg.communicate.return_value = (b"", b"")
            mock_ffmpeg.returncode = 0

            # Mock FFprobe for quality check
            mock_ffprobe = AsyncMock()
            mock_ffprobe.communicate.return_value = (b"10.5", b"")  # Valid duration
            mock_ffprobe.returncode = 0

            mock_subprocess.side_effect = [mock_ffmpeg, mock_ffprobe]

            success = await media_service.preprocess_audio(input_file, output_file)
            assert success is True

    @pytest.mark.asyncio
    async def test_preprocess_audio_ffmpeg_error(self, media_service, temp_dir):
        """Test audio preprocessing with FFmpeg error."""
        input_file = temp_dir / "input.mp3"
        output_file = temp_dir / "output.wav"
        input_file.write_text("fake input audio")

        with patch("asyncio.create_subprocess_exec") as mock_subprocess:
            mock_process = AsyncMock()
            mock_process.communicate.return_value = (b"", b"FFmpeg error")
            mock_process.returncode = 1
            mock_subprocess.return_value = mock_process

            success = await media_service.preprocess_audio(input_file, output_file)
            assert success is False

    @pytest.mark.asyncio
    async def test_download_media_mock(self, media_service, temp_dir):
        """Test media download with mocked yt-dlp."""
        test_url = "https://www.youtube.com/watch?v=test123"

        # Mock yt-dlp
        mock_info = {
            "title": "Test Video",
            "filesize": 1024 * 1024,  # 1MB
            "duration": 120,
            "format": "mp4"
        }

        with patch("yt_dlp.YoutubeDL") as mock_ydl_class:
            mock_ydl = MagicMock()
            mock_ydl.extract_info.return_value = mock_info
            mock_ydl_class.return_value.__enter__.return_value = mock_ydl

            # Create a fake downloaded file
            downloaded_file = temp_dir / "Test Video.mp4"
            downloaded_file.write_text("fake video content")

            # Mock file operations
            with patch.object(media_service, "get_media_info") as mock_get_info:
                mock_get_info.return_value = {
                    "duration": 120.0,
                    "mime_type": "mp4"
                }

                with patch.object(media_service, "_calculate_file_hash") as mock_hash:
                    mock_hash.return_value = "test_hash_123"

                    result = await media_service.download_media(test_url, temp_dir)

                    assert result.filename == "Test Video.mp4"
                    assert result.source_path == test_url
                    assert result.file_hash == "test_hash_123"

    @pytest.mark.asyncio
    async def test_download_media_file_too_large(self, media_service, temp_dir):
        """Test media download with file size validation."""
        test_url = "https://www.youtube.com/watch?v=test123"

        # Mock yt-dlp with large file
        mock_info = {
            "title": "Test Video",
            "filesize": 600 * 1024 * 1024,  # 600MB (exceeds 500MB limit)
            "duration": 120,
            "format": "mp4"
        }

        with patch("yt_dlp.YoutubeDL") as mock_ydl_class:
            mock_ydl = MagicMock()
            mock_ydl.extract_info.return_value = mock_info
            mock_ydl_class.return_value.__enter__.return_value = mock_ydl

            # The retry logic will catch the ValueError and retry, but since we're mocking
            # the same response each time, it will eventually fail with RetryError
            with pytest.raises(RetryError):
                await media_service.download_media(test_url, temp_dir)

    def test_create_media_service(self):
        """Test factory function for creating media service."""
        service = create_media_service()
        assert isinstance(service, MediaService)
        assert service.name == "media_service"

        # Test with custom config
        custom_config = {"max_file_size_mb": 1000, "retry_attempts": 5}
        service = create_media_service(custom_config)
        assert service.max_file_size_mb == 1000
        assert service.retry_attempts == 5

    def test_media_service_protocol(self):
        """Test that MediaService implements MediaServiceProtocol."""
        service = MediaService()
        assert isinstance(service, MediaServiceProtocol)

    @pytest.mark.asyncio
    async def test_progress_hook(self, media_service):
        """Test progress hook functionality."""
        # Test downloading progress
        progress_data = {
            "status": "downloading",
            "downloaded_bytes": 1024,
            "total_bytes": 2048,
            "speed": 1000,
            "eta": 1
        }
        
        # This should not raise any exceptions
        media_service._progress_hook(progress_data)

        # Test finished status
        finished_data = {"status": "finished"}
        media_service._progress_hook(finished_data)

    @pytest.mark.asyncio
    async def test_calculate_file_hash(self, media_service, temp_dir):
        """Test file hash calculation."""
        test_file = temp_dir / "test.txt"
        test_content = "test content for hashing"
        test_file.write_text(test_content)

        file_hash = await media_service._calculate_file_hash(test_file)
        
        # Should be a valid SHA-256 hash (64 characters)
        assert len(file_hash) == 64
        assert all(c in "0123456789abcdef" for c in file_hash)
