#!/usr/bin/env python3
"""
Unit tests for Instagram Repost Detector

Run with: python3 -m pytest tests/test_instagram_repost_detector.py -v
"""

import os
import sys
import tempfile
import shutil
from pathlib import Path
from unittest.mock import Mock, MagicMock, patch

# Add parent directory to path
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

try:
    import pytest
    PYTEST_AVAILABLE = True
except ImportError:
    PYTEST_AVAILABLE = False
    print("pytest not installed - run: pip3 install pytest")

from modules.instagram_repost_detector import InstagramRepostDetector


class TestInstagramRepostDetector:
    """Test suite for InstagramRepostDetector"""

    @pytest.fixture
    def mock_db(self):
        """Create mock database"""
        db = Mock()
        db.get_connection = MagicMock()
        return db

    @pytest.fixture
    def detector(self, mock_db):
        """Create detector instance with mock DB"""
        return InstagramRepostDetector(unified_db=mock_db)

    @pytest.fixture
    def temp_dir(self):
        """Create temporary directory for testing"""
        temp = tempfile.mkdtemp()
        yield Path(temp)
        shutil.rmtree(temp, ignore_errors=True)

    def test_detector_initialization(self, detector):
        """Test detector initializes correctly"""
        assert detector is not None
        assert detector.db is not None
        assert detector.temp_download_path.exists()

    def test_extract_username_from_text(self, detector):
        """Test username extraction from text"""
        # Mock pytesseract if available
        try:
            import pytesseract
            with patch('pytesseract.image_to_string') as mock_ocr:
                mock_ocr.return_value = "Check out @testuser's post!"

                # Create dummy image
                from PIL import Image
                with tempfile.NamedTemporaryFile(suffix='.jpg', delete=False) as f:
                    img = Image.new('RGB', (100, 100), color='white')
                    img.save(f.name)
                    temp_file = f.name

                result = detector._extract_username_from_repost(temp_file)
                os.unlink(temp_file)

                assert result == "testuser"
        except ImportError:
            pytest.skip("pytesseract not available")

    def test_extract_multiple_usernames(self, detector):
        """Test extraction when multiple @usernames present"""
        try:
            import pytesseract
            with patch('pytesseract.image_to_string') as mock_ocr:
                mock_ocr.return_value = "@firstuser and @seconduser posted this"

                from PIL import Image
                with tempfile.NamedTemporaryFile(suffix='.jpg', delete=False) as f:
                    img = Image.new('RGB', (100, 100), color='white')
                    img.save(f.name)
                    temp_file = f.name

                result = detector._extract_username_from_repost(temp_file)
                os.unlink(temp_file)

                # Should return first username found
                assert result == "firstuser"
        except ImportError:
            pytest.skip("pytesseract not available")

    def test_no_username_found(self, detector):
        """Test when no @username is found"""
        try:
            import pytesseract
            with patch('pytesseract.image_to_string') as mock_ocr:
                mock_ocr.return_value = "No username here"

                from PIL import Image
                with tempfile.NamedTemporaryFile(suffix='.jpg', delete=False) as f:
                    img = Image.new('RGB', (100, 100), color='white')
                    img.save(f.name)
                    temp_file = f.name

                result = detector._extract_username_from_repost(temp_file)
                os.unlink(temp_file)

                assert result is None
        except ImportError:
            pytest.skip("pytesseract not available")

    def test_is_monitored_account(self, detector, mock_db):
        """Test monitored account checking"""
        # Mock database response - user is monitored
        mock_cursor = Mock()
        mock_cursor.fetchone.return_value = (1,)
        mock_conn = Mock()
        mock_conn.cursor.return_value = mock_cursor
        mock_conn.__enter__ = Mock(return_value=mock_conn)
        mock_conn.__exit__ = Mock(return_value=False)
        mock_db.get_connection.return_value = mock_conn

        result = detector._is_monitored_account("testuser")
        assert result is True

    def test_is_not_monitored_account(self, detector, mock_db):
        """Test non-monitored account checking"""
        # Mock database response - user not monitored
        mock_cursor = Mock()
        mock_cursor.fetchone.return_value = None
        mock_conn = Mock()
        mock_conn.cursor.return_value = mock_cursor
        mock_conn.__enter__ = Mock(return_value=mock_conn)
        mock_conn.__exit__ = Mock(return_value=False)
        mock_db.get_connection.return_value = mock_conn

        result = detector._is_monitored_account("randomuser")
        assert result is False

    def test_already_fetched_today(self, detector, mock_db):
        """Test fetch cache checking"""
        # Mock database response - already fetched
        mock_cursor = Mock()
        mock_cursor.fetchone.return_value = ("2025-11-09T10:00:00",)
        mock_conn = Mock()
        mock_conn.cursor.return_value = mock_cursor
        mock_conn.__enter__ = Mock(return_value=mock_conn)
        mock_conn.__exit__ = Mock(return_value=False)
        mock_db.get_connection.return_value = mock_conn

        result = detector._already_fetched_today("testuser")
        assert result is True

    def test_not_fetched_today(self, detector, mock_db):
        """Test fetch cache when not fetched"""
        # Mock database response - not fetched
        mock_cursor = Mock()
        mock_cursor.fetchone.return_value = None
        mock_conn = Mock()
        mock_conn.cursor.return_value = mock_cursor
        mock_conn.__enter__ = Mock(return_value=mock_conn)
        mock_conn.__exit__ = Mock(return_value=False)
        mock_db.get_connection.return_value = mock_conn

        result = detector._already_fetched_today("testuser")
        assert result is False

    def test_perceptual_hash_calculation(self, detector):
        """Test perceptual hash calculation for images"""
        try:
            from PIL import Image
            import imagehash

            # Create test image
            with tempfile.NamedTemporaryFile(suffix='.jpg', delete=False) as f:
                img = Image.new('RGB', (100, 100), color='red')
                img.save(f.name)
                temp_file = f.name

            hash_result = detector._get_perceptual_hash(temp_file)
            os.unlink(temp_file)

            assert hash_result is not None
            assert isinstance(hash_result, imagehash.ImageHash)

        except ImportError:
            pytest.skip("PIL or imagehash not available")

    def test_perceptual_hash_similarity(self, detector):
        """Test that similar images produce similar hashes"""
        try:
            from PIL import Image, ImageDraw
            import imagehash

            # Create two similar images
            with tempfile.NamedTemporaryFile(suffix='.jpg', delete=False) as f1:
                img1 = Image.new('RGB', (100, 100), color='blue')
                draw = ImageDraw.Draw(img1)
                draw.rectangle([25, 25, 75, 75], fill='white')
                img1.save(f1.name)
                temp_file1 = f1.name

            with tempfile.NamedTemporaryFile(suffix='.jpg', delete=False) as f2:
                img2 = Image.new('RGB', (100, 100), color='blue')
                draw = ImageDraw.Draw(img2)
                draw.rectangle([25, 25, 75, 75], fill='white')
                img2.save(f2.name)
                temp_file2 = f2.name

            hash1 = detector._get_perceptual_hash(temp_file1)
            hash2 = detector._get_perceptual_hash(temp_file2)

            os.unlink(temp_file1)
            os.unlink(temp_file2)

            # Identical images should have distance 0
            distance = hash1 - hash2
            assert distance == 0

        except ImportError:
            pytest.skip("PIL or imagehash not available")

    def test_cleanup_temp_downloads(self, detector, temp_dir):
        """Test cleanup of temporary files"""
        # Create test files
        test_dir = temp_dir / "testuser"
        stories_dir = test_dir / "stories"
        posts_dir = test_dir / "posts"
        stories_dir.mkdir(parents=True)
        posts_dir.mkdir(parents=True)

        # Create dummy files
        file1 = stories_dir / "story1.jpg"
        file2 = stories_dir / "story2.jpg"
        file3 = posts_dir / "post1.jpg"
        file1.touch()
        file2.touch()
        file3.touch()

        # Cleanup, keeping file1
        detector._cleanup_temp_downloads(test_dir, keep_file=str(file1))

        # Check results
        assert file1.exists()  # Should be kept
        assert not file2.exists()  # Should be deleted
        assert not file3.exists()  # Should be deleted

    def test_file_not_found(self, detector):
        """Test handling of non-existent files"""
        result = detector.check_and_replace_repost("/nonexistent/file.jpg", "testuser")
        assert result is None

    def test_mark_fetched(self, detector, mock_db):
        """Test marking content as fetched"""
        mock_cursor = Mock()
        mock_conn = Mock()
        mock_conn.cursor.return_value = mock_cursor
        mock_conn.__enter__ = Mock(return_value=mock_conn)
        mock_conn.__exit__ = Mock(return_value=False)
        mock_db.get_connection.return_value = mock_conn

        # Should not raise exception
        detector._mark_fetched("testuser", content_count=10)

        # Verify INSERT was called
        assert mock_cursor.execute.called

    def test_record_replacement(self, detector, mock_db):
        """Test recording repost replacement"""
        mock_cursor = Mock()
        mock_conn = Mock()
        mock_conn.cursor.return_value = mock_cursor
        mock_conn.__enter__ = Mock(return_value=mock_conn)
        mock_conn.__exit__ = Mock(return_value=False)
        mock_db.get_connection.return_value = mock_conn

        detector.last_original_username = "originaluser"

        # Should not raise exception
        detector._record_repost_replacement(
            repost_path="/path/to/repost.jpg",
            original_path="/path/to/original.jpg",
            replacement_path="/path/to/replacement.jpg"
        )

        # Verify INSERT was called
        assert mock_cursor.execute.called


# Integration-style tests
class TestInstagramRepostDetectorIntegration:
    """Integration tests with real file operations"""

    @pytest.fixture
    def temp_dir(self):
        """Create temporary directory"""
        temp = tempfile.mkdtemp()
        yield Path(temp)
        shutil.rmtree(temp, ignore_errors=True)

    def test_full_workflow_mock(self, temp_dir):
        """Test full workflow with mocked downloads"""
        # This would test the complete flow:
        # 1. Create repost file with @username
        # 2. OCR extracts username
        # 3. Check if monitored
        # 4. Download content (mocked)
        # 5. Find match via hash
        # 6. Replace file
        # 7. Cleanup temp files

        # Skipped for now - would require extensive mocking
        pytest.skip("Full integration test requires complete setup")


if __name__ == "__main__":
    if PYTEST_AVAILABLE:
        pytest.main([__file__, "-v"])
    else:
        print("Please install pytest: pip3 install pytest")
