#!/usr/bin/env python3
"""
Test suite for Supadata YouTube Transcript Fetcher

Run with: python test_fetch_transcript.py
"""

import unittest
import sys
from pathlib import Path
from unittest.mock import Mock, patch

# Add script directory to path
sys.path.insert(0, str(Path(__file__).parent))

from fetch_transcript import SupadataClient, load_api_key


class TestVideoIdExtraction(unittest.TestCase):
    """Test video ID extraction from various URL formats"""

    def setUp(self):
        self.client = SupadataClient("test_api_key")

    def test_direct_video_id(self):
        """Test direct 11-character video ID"""
        self.assertEqual(self.client.extract_video_id("dQw4w9WgXcQ"), "dQw4w9WgXcQ")

    def test_standard_youtube_url(self):
        """Test standard youtube.com/watch?v= format"""
        url = "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
        self.assertEqual(self.client.extract_video_id(url), "dQw4w9WgXcQ")

    def test_short_youtube_url(self):
        """Test youtu.be short URL format"""
        url = "https://youtu.be/dQw4w9WgXcQ"
        self.assertEqual(self.client.extract_video_id(url), "dQw4w9WgXcQ")

    def test_embed_url(self):
        """Test youtube.com/embed/ format"""
        url = "https://www.youtube.com/embed/dQw4w9WgXcQ"
        self.assertEqual(self.client.extract_video_id(url), "dQw4w9WgXcQ")

    def test_shorts_url(self):
        """Test youtube.com/shorts/ format"""
        url = "https://www.youtube.com/shorts/dQw4w9WgXcQ"
        self.assertEqual(self.client.extract_video_id(url), "dQw4w9WgXcQ")

    def test_url_with_additional_params(self):
        """Test URL with additional query parameters"""
        url = "https://www.youtube.com/watch?v=dQw4w9WgXcQ&t=42s&list=PLx"
        self.assertEqual(self.client.extract_video_id(url), "dQw4w9WgXcQ")

    def test_invalid_url_raises(self):
        """Test that invalid URLs raise ValueError"""
        with self.assertRaises(ValueError):
            self.client.extract_video_id("not-a-youtube-url")


class TestApiKeyLoading(unittest.TestCase):
    """Test API key loading from various sources"""

    @patch.dict('os.environ', {'SUPADATA_API_KEY': 'env_api_key'})
    def test_load_from_environment(self):
        """Test loading API key from environment variable"""
        key = load_api_key()
        self.assertEqual(key, 'env_api_key')

    @patch.dict('os.environ', {}, clear=True)
    @patch('pathlib.Path.exists')
    @patch('builtins.open')
    def test_load_from_secrets_file(self, mock_open, mock_exists):
        """Test loading API key from secrets.env file"""
        mock_exists.return_value = True
        mock_open.return_value.__enter__.return_value = iter([
            '# Comment line\n',
            'OTHER_KEY=value\n',
            'SUPADATA_API_KEY=file_api_key\n'
        ])

        key = load_api_key()
        self.assertEqual(key, 'file_api_key')

    @patch.dict('os.environ', {}, clear=True)
    @patch('pathlib.Path.exists')
    def test_missing_api_key_raises(self, mock_exists):
        """Test that missing API key raises ValueError"""
        mock_exists.return_value = False

        with self.assertRaises(ValueError) as context:
            load_api_key()

        self.assertIn("SUPADATA_API_KEY not found", str(context.exception))


class TestTranscriptFetching(unittest.TestCase):
    """Test transcript fetching with mocked API responses"""

    def setUp(self):
        self.client = SupadataClient("test_api_key")

    @patch('requests.Session.get')
    def test_successful_fetch(self, mock_get):
        """Test successful transcript fetch"""
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = {
            "content": [{"text": "Hello world", "offset": 0, "duration": 1000}],
            "lang": "en",
            "availableLangs": ["en"]
        }
        mock_get.return_value = mock_response

        result = self.client.get_transcript("dQw4w9WgXcQ")

        self.assertEqual(result["lang"], "en")
        self.assertEqual(result["video_id"], "dQw4w9WgXcQ")
        self.assertIn("fetched_at", result)

    @patch('requests.Session.get')
    def test_404_error(self, mock_get):
        """Test handling of 404 not found error"""
        mock_response = Mock()
        mock_response.status_code = 404
        mock_get.return_value = mock_response

        # Use valid 11-char video ID format so we test the API error, not ID extraction
        with self.assertRaises(ValueError) as context:
            self.client.get_transcript("xYz123AbCdE")

        self.assertIn("not found", str(context.exception))

    @patch('requests.Session.get')
    def test_rate_limit_error(self, mock_get):
        """Test handling of 429 rate limit error"""
        mock_response = Mock()
        mock_response.status_code = 429
        mock_get.return_value = mock_response

        with self.assertRaises(RuntimeError) as context:
            self.client.get_transcript("dQw4w9WgXcQ")

        self.assertIn("Rate limited", str(context.exception))


class TestAsyncJobPolling(unittest.TestCase):
    """Test async job polling for long videos"""

    def setUp(self):
        self.client = SupadataClient("test_api_key")

    @patch('requests.Session.get')
    @patch('time.sleep')  # Skip sleep in tests
    def test_async_job_polling(self, mock_sleep, mock_get):
        """Test polling for async job completion"""
        # First call returns 202 with job ID
        initial_response = Mock()
        initial_response.status_code = 202
        initial_response.json.return_value = {"jobId": "job_123"}

        # Second call returns completed job
        completed_response = Mock()
        completed_response.status_code = 200
        completed_response.json.return_value = {
            "content": "Transcript content",
            "lang": "en"
        }

        mock_get.side_effect = [initial_response, completed_response]

        result = self.client.get_transcript("dQw4w9WgXcQ")

        self.assertEqual(result["lang"], "en")
        self.assertEqual(mock_get.call_count, 2)


if __name__ == '__main__':
    # Run tests with verbosity
    unittest.main(verbosity=2)
