import pytest
from unittest.mock import Mock, patch, MagicMock, PropertyMock
from datetime import datetime
from pathlib import Path
import random
from src.instagram_scraper import InstagramScraper
from src.base_scraper import ScraperConfig


class TestInstagramScraper:
    @pytest.fixture
    def config(self):
        return ScraperConfig(
            source_name="instagram",
            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', {
            'INSTAGRAM_USERNAME': 'testuser',
            'INSTAGRAM_PASSWORD': 'testpass',
            'INSTAGRAM_TARGET': 'hvacknowitall'
        }):
            yield

    @pytest.fixture
    def sample_post(self):
        mock_post = MagicMock()
        mock_post.shortcode = 'ABC123'
        mock_post.caption = 'Test caption #hvac #tips'
        mock_post.owner_username = 'hvacknowitall'
        mock_post.date_utc = datetime(2024, 1, 1, 12, 0, 0)
        mock_post.typename = 'GraphImage'
        mock_post.url = 'https://www.instagram.com/p/ABC123/'
        mock_post.likes = 150
        mock_post.comments = 25
        mock_post.video_view_count = None
        mock_post.mediacount = 1
        mock_post.caption_hashtags = ['hvac', 'tips']
        mock_post.caption_mentions = []
        mock_post.is_video = False  # Explicitly set is_video to False
        return mock_post

    @pytest.fixture
    def sample_story(self):
        mock_story = MagicMock()
        mock_story.mediaid = 123456789
        mock_story.owner_username = 'hvacknowitall'
        mock_story.date_utc = datetime(2024, 1, 1, 12, 0, 0)
        mock_story.url = 'https://www.instagram.com/stories/hvacknowitall/123456789/'
        mock_story.typename = 'GraphStoryImage'
        mock_story.is_video = False  # Explicitly set is_video to False
        return mock_story

    @patch('src.instagram_scraper.InstagramScraper._login')
    @patch('src.instagram_scraper.InstagramScraper._setup_loader')
    def test_initialization(self, mock_setup, mock_login, config, mock_env):
        mock_setup.return_value = MagicMock()
        scraper = InstagramScraper(config)
        assert scraper.config == config
        assert scraper.username == 'testuser'
        assert scraper.password == 'testpass'
        assert scraper.target_account == 'hvacknowitall'

    @patch('src.instagram_scraper.InstagramScraper._login')
    @patch('instaloader.Instaloader')
    def test_setup_loader(self, mock_instaloader_class, mock_login, config, mock_env):
        mock_loader = MagicMock()
        mock_instaloader_class.return_value = mock_loader
        
        scraper = InstagramScraper(config)
        
        # Test that instaloader was initialized with correct params
        mock_instaloader_class.assert_called_once()
        call_kwargs = mock_instaloader_class.call_args[1]
        assert call_kwargs['quiet'] == True
        assert call_kwargs['download_videos'] == False
        assert call_kwargs['download_video_thumbnails'] == False

    @patch('src.instagram_scraper.InstagramScraper._setup_loader')
    @patch('instaloader.Instaloader')
    def test_login(self, mock_instaloader_class, mock_setup, config, mock_env):
        mock_loader = MagicMock()
        mock_setup.return_value = mock_loader
        
        # Create scraper without triggering login in __init__
        with patch('src.instagram_scraper.InstagramScraper._login'):
            scraper = InstagramScraper(config)
            scraper.loader = mock_loader
        
        # Now test login
        scraper._login()
        
        # Should try to login with credentials since no session file exists
        mock_loader.login.assert_called_once_with('testuser', 'testpass')

    @patch('time.sleep')
    @patch('random.uniform')
    @patch('src.instagram_scraper.InstagramScraper._login')
    @patch('src.instagram_scraper.InstagramScraper._setup_loader')
    def test_aggressive_delay(self, mock_setup, mock_login, mock_uniform, mock_sleep, config, mock_env):
        mock_uniform.return_value = 7.5
        mock_setup.return_value = MagicMock()
        
        scraper = InstagramScraper(config)
        scraper._aggressive_delay()
        
        mock_uniform.assert_called_with(5, 10)
        mock_sleep.assert_called_with(7.5)

    @patch('instaloader.Profile.from_username')
    @patch('src.instagram_scraper.InstagramScraper._login')
    @patch('src.instagram_scraper.InstagramScraper._setup_loader')
    def test_fetch_posts(self, mock_setup, mock_login, mock_profile_from_username, 
                         config, mock_env, sample_post):
        mock_loader = MagicMock()
        mock_setup.return_value = mock_loader
        
        mock_profile = MagicMock()
        mock_profile.get_posts.return_value = [sample_post]
        mock_profile_from_username.return_value = mock_profile
        
        scraper = InstagramScraper(config)
        scraper.loader = mock_loader
        posts = scraper.fetch_posts(max_posts=10)
        
        assert len(posts) == 1
        assert posts[0]['id'] == 'ABC123'
        assert posts[0]['type'] == 'post'
        assert posts[0]['caption'] == 'Test caption #hvac #tips'

    @patch('instaloader.Profile.from_username')
    @patch('src.instagram_scraper.InstagramScraper._login')
    @patch('src.instagram_scraper.InstagramScraper._setup_loader')
    def test_fetch_stories(self, mock_setup, mock_login, mock_profile_from_username, 
                          config, mock_env, sample_story):
        mock_loader = MagicMock()
        mock_setup.return_value = mock_loader
        # get_stories returns an iterable where each element is an iterable of story items
        mock_loader.get_stories.return_value = [[sample_story]]  # Simplified: one story collection with one item
        
        mock_profile = MagicMock()
        mock_profile.userid = 12345
        mock_profile_from_username.return_value = mock_profile
        
        scraper = InstagramScraper(config)
        scraper.loader = mock_loader
        stories = scraper.fetch_stories()
        
        assert len(stories) == 1
        assert stories[0]['id'] == 123456789
        assert stories[0]['type'] == 'story'

    @patch('src.instagram_scraper.InstagramScraper._login')
    @patch('src.instagram_scraper.InstagramScraper._setup_loader')
    def test_get_post_type(self, mock_setup, mock_login, config, mock_env):
        mock_setup.return_value = MagicMock()
        scraper = InstagramScraper(config)
        
        mock_post = MagicMock()
        
        # Test regular post
        mock_post.typename = 'GraphImage'
        mock_post.is_video = False
        assert scraper._get_post_type(mock_post) == 'post'
        
        # Test video/reel
        mock_post.typename = 'GraphVideo'
        mock_post.is_video = True
        assert scraper._get_post_type(mock_post) == 'reel'
        
        # Test carousel
        mock_post.typename = 'GraphSidecar'
        mock_post.is_video = False
        assert scraper._get_post_type(mock_post) == 'post'

    @patch('src.instagram_scraper.InstagramScraper._login')
    @patch('src.instagram_scraper.InstagramScraper._setup_loader')
    def test_format_markdown(self, mock_setup, mock_login, config, mock_env):
        mock_setup.return_value = MagicMock()
        scraper = InstagramScraper(config)
        
        items = [
            {
                'id': 'ABC123',
                'type': 'post',
                'caption': 'Test post',
                'author': 'hvacknowitall',
                'publish_date': '2024-01-01T12:00:00',
                'link': 'https://www.instagram.com/p/ABC123/',
                'likes': 150,
                'comments': 25,
                'views': None,
                'hashtags': ['hvac', 'tips']
            }
        ]
        
        markdown = scraper.format_markdown(items)
        
        assert '# ID: ABC123' in markdown
        assert '## Type: post' in markdown
        assert '## Author: hvacknowitall' in markdown
        assert '## Publish Date: 2024-01-01T12:00:00' in markdown
        assert '## Link: https://www.instagram.com/p/ABC123/' in markdown
        assert '## Likes: 150' in markdown
        assert '## Comments: 25' in markdown
        assert '## Hashtags: hvac, tips' in markdown
        assert 'Test post' in markdown

    @patch('src.instagram_scraper.InstagramScraper._login')
    @patch('src.instagram_scraper.InstagramScraper._setup_loader')
    def test_get_incremental_items(self, mock_setup, mock_login, config, mock_env):
        mock_setup.return_value = MagicMock()
        scraper = InstagramScraper(config)
        
        items = [
            {'id': 'post3', 'publish_date': '2024-01-03T12:00:00'},
            {'id': 'post2', 'publish_date': '2024-01-02T12:00:00'},
            {'id': 'post1', 'publish_date': '2024-01-01T12:00:00'}
        ]
        
        # Test with no previous state
        state = {}
        new_items = scraper.get_incremental_items(items, state)
        assert len(new_items) == 3
        
        # Test with existing state
        state = {'last_post_id': 'post2'}
        new_items = scraper.get_incremental_items(items, state)
        assert len(new_items) == 1
        assert new_items[0]['id'] == 'post3'

    @patch('src.instagram_scraper.InstagramScraper._login')
    @patch('src.instagram_scraper.InstagramScraper._setup_loader')
    def test_update_state(self, mock_setup, mock_login, config, mock_env):
        mock_setup.return_value = MagicMock()
        scraper = InstagramScraper(config)
        
        state = {}
        items = [
            {'id': 'post2', 'publish_date': '2024-01-02T12:00:00', 'type': 'post'},
            {'id': 'post1', 'publish_date': '2024-01-01T12:00:00', 'type': 'post'}
        ]
        
        updated_state = scraper.update_state(state, items)
        
        assert updated_state['last_post_id'] == 'post2'
        assert updated_state['last_post_date'] == '2024-01-02T12:00:00'
        assert updated_state['post_count'] == 2

    @patch('src.instagram_scraper.InstagramScraper._setup_loader')
    @patch('instaloader.Instaloader')
    def test_error_handling(self, mock_instaloader_class, mock_setup, config, mock_env):
        mock_loader = MagicMock()
        mock_setup.return_value = mock_loader
        mock_loader.login.side_effect = Exception("Login failed")
        
        # Test that login error is handled gracefully
        with patch('src.instagram_scraper.InstagramScraper._login'):
            scraper = InstagramScraper(config)
            scraper.loader = mock_loader
        
        scraper._login()  # Should not raise, just log error
        
        # Test fetch error handling
        posts = scraper.fetch_posts()
        assert posts == []