import pytest
from unittest.mock import Mock, patch, MagicMock, AsyncMock
from datetime import datetime
from pathlib import Path
import asyncio
from src.tiktok_scraper import TikTokScraper
from src.base_scraper import ScraperConfig


class TestTikTokScraper:
    @pytest.fixture
    def config(self):
        return ScraperConfig(
            source_name="tiktok",
            brand_name="hvacknowitall",
            data_dir=Path("data"),
            logs_dir=Path("logs"),
            timezone="America/Halifax"
        )
    
    @pytest.fixture
    def mock_env(self):
        with patch.dict('os.environ', {
            'TIKTOK_USERNAME': 'test@example.com',
            'TIKTOK_PASSWORD': 'testpass',
            'TIKTOK_TARGET': 'hvacknowitall'
        }):
            yield

    @pytest.fixture
    def sample_video(self):
        mock_video = MagicMock()
        mock_video.id = '7234567890123456789'
        mock_video.author.username = 'hvacknowitall'
        mock_video.author.nickname = 'HVAC Know It All'
        mock_video.desc = 'Check out this HVAC tip! #hvac #maintenance'
        mock_video.create_time = 1704134400  # 2024-01-01 12:00:00 UTC
        mock_video.stats.play_count = 15000
        mock_video.stats.comment_count = 250
        mock_video.stats.share_count = 50
        mock_video.stats.collect_count = 100  # Likes/favorites
        mock_video.music.title = 'Original sound'
        mock_video.duration = 30
        mock_video.hashtags = ['hvac', 'maintenance']
        return mock_video

    @patch('src.tiktok_scraper.TikTokScraper._setup_api')
    def test_initialization(self, mock_setup, config, mock_env):
        mock_setup.return_value = AsyncMock()
        scraper = TikTokScraper(config)
        assert scraper.config == config
        assert scraper.username == 'test@example.com'
        assert scraper.password == 'testpass'
        assert scraper.target_account == 'hvacknowitall'

    @patch('src.tiktok_scraper.TikTokScraper._setup_api')
    def test_humanized_delay(self, mock_setup, config, mock_env):
        mock_setup.return_value = AsyncMock()
        scraper = TikTokScraper(config)
        
        with patch('time.sleep') as mock_sleep:
            with patch('random.uniform', return_value=3.5):
                scraper._humanized_delay()
                mock_sleep.assert_called_with(3.5)

    @pytest.mark.asyncio
    @patch('src.tiktok_scraper.TikTokApi')
    @patch('src.tiktok_scraper.TikTokScraper._setup_api')
    async def test_fetch_user_videos(self, mock_setup, mock_tiktokapi_class, config, mock_env, sample_video):
        # Create a simpler mock that doesn't use AsyncMock
        mock_api = MagicMock()
        mock_setup.return_value = mock_api
        
        # Setup async context manager
        mock_api.__aenter__ = AsyncMock(return_value=mock_api)
        mock_api.__aexit__ = AsyncMock(return_value=None)
        mock_api.create_sessions = AsyncMock(return_value=None)
        
        # Mock user
        mock_user = MagicMock()
        mock_api.user.return_value = mock_user
        
        # Create async generator for videos
        async def video_generator(count=None):
            yield sample_video
        
        mock_user.videos = video_generator
        
        scraper = TikTokScraper(config)
        scraper.api = mock_api
        
        videos = await scraper.fetch_user_videos(max_videos=10)
        
        assert len(videos) == 1
        assert videos[0]['id'] == '7234567890123456789'
        assert videos[0]['author'] == 'hvacknowitall'
        assert videos[0]['description'] == 'Check out this HVAC tip! #hvac #maintenance'

    @patch('src.tiktok_scraper.TikTokScraper._setup_api')
    def test_format_markdown(self, mock_setup, config, mock_env):
        mock_setup.return_value = AsyncMock()
        scraper = TikTokScraper(config)
        
        videos = [
            {
                'id': '7234567890123456789',
                'author': 'hvacknowitall',
                'nickname': 'HVAC Know It All',
                'description': 'HVAC maintenance tips',
                'publish_date': '2024-01-01T12:00:00',
                'link': 'https://www.tiktok.com/@hvacknowitall/video/7234567890123456789',
                'views': 15000,
                'likes': 100,
                'comments': 250,
                'shares': 50,
                'duration': 30,
                'music': 'Original sound',
                'hashtags': ['hvac', 'maintenance']
            }
        ]
        
        markdown = scraper.format_markdown(videos)
        
        assert '# ID: 7234567890123456789' in markdown
        assert '## Author: hvacknowitall' in markdown
        assert '## Nickname: HVAC Know It All' in markdown
        assert '## Description:' in markdown
        assert 'HVAC maintenance tips' in markdown
        assert '## Views: 15000' in markdown
        assert '## Likes: 100' in markdown
        assert '## Comments: 250' in markdown
        assert '## Shares: 50' in markdown
        assert '## Duration: 30 seconds' in markdown
        assert '## Music: Original sound' in markdown
        assert '## Hashtags: hvac, maintenance' in markdown

    @patch('src.tiktok_scraper.TikTokScraper._setup_api')
    def test_get_incremental_items(self, mock_setup, config, mock_env):
        mock_setup.return_value = AsyncMock()
        scraper = TikTokScraper(config)
        
        videos = [
            {'id': 'video3', 'publish_date': '2024-01-03T12:00:00'},
            {'id': 'video2', 'publish_date': '2024-01-02T12:00:00'},
            {'id': 'video1', 'publish_date': '2024-01-01T12:00:00'}
        ]
        
        # Test with no previous state
        state = {}
        new_videos = scraper.get_incremental_items(videos, state)
        assert len(new_videos) == 3
        
        # Test with existing state
        state = {'last_video_id': 'video2'}
        new_videos = scraper.get_incremental_items(videos, state)
        assert len(new_videos) == 1
        assert new_videos[0]['id'] == 'video3'

    @patch('src.tiktok_scraper.TikTokScraper._setup_api')
    def test_update_state(self, mock_setup, config, mock_env):
        mock_setup.return_value = AsyncMock()
        scraper = TikTokScraper(config)
        
        state = {}
        videos = [
            {'id': 'video2', 'publish_date': '2024-01-02T12:00:00'},
            {'id': 'video1', 'publish_date': '2024-01-01T12:00:00'}
        ]
        
        updated_state = scraper.update_state(state, videos)
        
        assert updated_state['last_video_id'] == 'video2'
        assert updated_state['last_video_date'] == '2024-01-02T12:00:00'
        assert updated_state['video_count'] == 2

    @pytest.mark.asyncio
    @patch('src.tiktok_scraper.TikTokScraper._setup_api')
    async def test_error_handling(self, mock_setup, config, mock_env):
        mock_api = MagicMock()
        mock_setup.return_value = mock_api
        
        # Setup async context manager that raises error
        mock_api.__aenter__ = AsyncMock(side_effect=Exception("API Error"))
        mock_api.__aexit__ = AsyncMock(return_value=None)
        
        scraper = TikTokScraper(config)
        scraper.api = mock_api
        
        videos = await scraper.fetch_user_videos()
        assert videos == []

    @pytest.mark.asyncio
    @patch('src.tiktok_scraper.TikTokScraper._setup_api')
    async def test_fetch_content_wrapper(self, mock_setup, config, mock_env):
        mock_setup.return_value = MagicMock()
        
        scraper = TikTokScraper(config)
        
        # Mock the fetch_user_videos to return sample data
        async def mock_fetch():
            return [
                {
                    'id': '7234567890123456789',
                    'author': 'hvacknowitall',
                    'description': 'Test video'
                }
            ]
        
        scraper.fetch_user_videos = mock_fetch
        
        # Test the synchronous wrapper by running it in an async context
        import asyncio
        loop = asyncio.get_event_loop()
        videos = await loop.run_in_executor(None, scraper.fetch_content)
        
        assert len(videos) == 1
        assert videos[0]['id'] == '7234567890123456789'