# tests/integration/tasks/test_background_tasks.py
from unittest.mock import AsyncMock, MagicMock, patch

import pytest

from app.modules.marketplace.tasks import process_marketplace_import
from app.modules.marketplace.models import MarketplaceImportJob


@pytest.mark.integration
@pytest.mark.database
@pytest.mark.marketplace
class TestBackgroundTasks:
    @pytest.mark.asyncio
    async def test_marketplace_import_success(self, db, test_user, test_vendor):
        """Test successful marketplace import background task"""
        # Create import job
        job = MarketplaceImportJob(
            status="pending",
            source_url="http://example.com/test.csv",
            marketplace="TestMarket",
            vendor_id=test_vendor.id,
            user_id=test_user.id,
            language="en",
        )
        db.add(job)
        db.commit()
        db.refresh(job)

        # Store the job ID before it becomes detached
        job_id = job.id

        # Mock CSV processor and prevent session from closing
        with (
            patch("app.tasks.background_tasks.CSVProcessor") as mock_processor,
            patch("app.tasks.background_tasks.SessionLocal", return_value=db),
        ):
            mock_instance = mock_processor.return_value
            mock_instance.process_marketplace_csv_from_url = AsyncMock(
                return_value={
                    "imported": 10,
                    "updated": 5,
                    "total_processed": 15,
                    "errors": 0,
                }
            )

            # Run background task
            await process_marketplace_import(
                job_id, "http://example.com/test.csv", "TestMarket", test_vendor.id, 1000
            )

            # Re-query the job using the stored ID
            updated_job = (
                db.query(MarketplaceImportJob)
                .filter(MarketplaceImportJob.id == job_id)
                .first()
            )

            assert updated_job is not None
            assert updated_job.status == "completed"
            assert updated_job.imported_count == 10
            assert updated_job.updated_count == 5
            assert updated_job.total_processed == 15
            assert updated_job.error_count == 0
            assert updated_job.started_at is not None
            assert updated_job.completed_at is not None

    @pytest.mark.asyncio
    async def test_marketplace_import_failure(self, db, test_user, test_vendor):
        """Test marketplace import failure handling"""
        # Create import job
        job = MarketplaceImportJob(
            status="pending",
            source_url="http://example.com/test.csv",
            marketplace="TestMarket",
            vendor_id=test_vendor.id,
            user_id=test_user.id,
            language="en",
        )
        db.add(job)
        db.commit()
        db.refresh(job)

        # Store the job ID before it becomes detached
        job_id = job.id

        # Mock CSV processor to raise exception
        with (
            patch("app.tasks.background_tasks.CSVProcessor") as mock_processor,
            patch("app.tasks.background_tasks.SessionLocal", return_value=db),
        ):
            mock_instance = mock_processor.return_value
            mock_instance.process_marketplace_csv_from_url = AsyncMock(
                side_effect=Exception("Import failed")
            )

            # Run background task - this should not raise the exception
            # because it's handled in the background task
            try:
                await process_marketplace_import(
                    job_id,
                    "http://example.com/test.csv",
                    "TestMarket",
                    test_vendor.id,
                    1000,
                )
            except Exception:
                # The background task should handle exceptions internally
                # If an exception propagates here, that's a bug in the background task
                pass

            # Re-query the job using the stored ID
            updated_job = (
                db.query(MarketplaceImportJob)
                .filter(MarketplaceImportJob.id == job_id)
                .first()
            )

            assert updated_job is not None
            assert updated_job.status == "failed"
            assert "Import failed" in updated_job.error_message

    @pytest.mark.asyncio
    async def test_marketplace_import_job_not_found(self, db):
        """Test handling when import job doesn't exist"""
        with (
            patch("app.tasks.background_tasks.CSVProcessor") as mock_processor,
            patch("app.tasks.background_tasks.SessionLocal", return_value=db),
        ):
            mock_instance = mock_processor.return_value
            mock_instance.process_marketplace_csv_from_url = AsyncMock(
                return_value={
                    "imported": 10,
                    "updated": 5,
                    "total_processed": 15,
                    "errors": 0,
                }
            )

            # Run background task with non-existent job ID
            await process_marketplace_import(
                999,  # Non-existent job ID
                "http://example.com/test.csv",
                "TestMarket",
                "TESTVENDOR",
                1000,
            )

            # Should not raise an exception, just log and return
            # The CSV processor should not be called
            mock_instance.process_marketplace_csv_from_url.assert_not_called()

    @pytest.mark.asyncio
    async def test_marketplace_import_with_errors(self, db, test_user, test_vendor):
        """Test marketplace import with some errors"""
        # Create import job
        job = MarketplaceImportJob(
            status="pending",
            source_url="http://example.com/test.csv",
            marketplace="TestMarket",
            vendor_id=test_vendor.id,
            user_id=test_user.id,
            language="en",
        )
        db.add(job)
        db.commit()
        db.refresh(job)

        # Store the job ID before it becomes detached
        job_id = job.id

        # Mock CSV processor with some errors
        with (
            patch("app.tasks.background_tasks.CSVProcessor") as mock_processor,
            patch("app.tasks.background_tasks.SessionLocal", return_value=db),
        ):
            mock_instance = mock_processor.return_value
            mock_instance.process_marketplace_csv_from_url = AsyncMock(
                return_value={
                    "imported": 8,
                    "updated": 5,
                    "total_processed": 15,
                    "errors": 2,
                }
            )

            # Run background task
            await process_marketplace_import(
                job_id, "http://example.com/test.csv", "TestMarket", test_vendor.id, 1000
            )

            # Re-query the job using the stored ID
            updated_job = (
                db.query(MarketplaceImportJob)
                .filter(MarketplaceImportJob.id == job_id)
                .first()
            )

            assert updated_job is not None
            assert updated_job.status == "completed_with_errors"
            assert updated_job.imported_count == 8
            assert updated_job.updated_count == 5
            assert updated_job.error_count == 2
            assert updated_job.total_processed == 15
            assert "2 rows had errors" in updated_job.error_message

    @pytest.mark.asyncio
    async def test_marketplace_import_vendor_not_found(self, db, test_user, test_vendor):
        """Test handling when vendor doesn't exist"""
        # Create import job
        job = MarketplaceImportJob(
            status="pending",
            source_url="http://example.com/test.csv",
            marketplace="TestMarket",
            vendor_id=test_vendor.id,
            user_id=test_user.id,
            language="en",
        )
        db.add(job)
        db.commit()
        db.refresh(job)

        # Store the job ID before it becomes detached
        job_id = job.id

        # Mock CSV processor
        with (
            patch("app.tasks.background_tasks.CSVProcessor") as mock_processor,
            patch("app.tasks.background_tasks.SessionLocal", return_value=db),
        ):
            mock_instance = mock_processor.return_value
            mock_instance.process_marketplace_csv_from_url = AsyncMock(
                return_value={
                    "imported": 10,
                    "updated": 5,
                    "total_processed": 15,
                    "errors": 0,
                }
            )

            # Run background task with non-existent vendor ID
            await process_marketplace_import(
                job_id,
                "http://example.com/test.csv",
                "TestMarket",
                99999,  # Non-existent vendor ID
                1000,
            )

            # The CSV processor should not be called
            mock_instance.process_marketplace_csv_from_url.assert_not_called()

            # Re-query the job using the stored ID
            updated_job = (
                db.query(MarketplaceImportJob)
                .filter(MarketplaceImportJob.id == job_id)
                .first()
            )

            assert updated_job is not None
            assert updated_job.status == "failed"
            assert "Vendor 99999 not found" in updated_job.error_message
            assert updated_job.completed_at is not None

    @pytest.mark.asyncio
    async def test_marketplace_import_commit_error_handling(
        self, db, test_user, test_vendor
    ):
        """Test handling when commit fails during exception handling"""
        # Create import job
        job = MarketplaceImportJob(
            status="pending",
            source_url="http://example.com/test.csv",
            marketplace="TestMarket",
            vendor_id=test_vendor.id,
            user_id=test_user.id,
            language="en",
        )
        db.add(job)
        db.commit()
        db.refresh(job)

        # Store the job ID before it becomes detached
        job_id = job.id

        # Create a mock session that will fail on commit in the exception handler
        mock_session = MagicMock()
        mock_session.query.return_value.filter.return_value.first.side_effect = [
            job,  # First call returns the job
            test_vendor,  # Second call returns the vendor
        ]
        mock_session.commit.side_effect = [
            None,  # First commit (status=processing) succeeds
            Exception("Simulated commit failure"),  # Second commit fails
        ]
        mock_session.rollback = MagicMock()
        mock_session.close = MagicMock()

        with (
            patch("app.tasks.background_tasks.CSVProcessor") as mock_processor,
            patch(
                "app.tasks.background_tasks.SessionLocal", return_value=mock_session
            ),
        ):
            mock_instance = mock_processor.return_value
            mock_instance.process_marketplace_csv_from_url = AsyncMock(
                side_effect=Exception("Import failed")
            )

            # Run background task - should handle commit error gracefully
            await process_marketplace_import(
                job_id,
                "http://example.com/test.csv",
                "TestMarket",
                test_vendor.id,
                1000,
            )

            # Verify rollback was called after commit failure
            mock_session.rollback.assert_called_once()

    @pytest.mark.asyncio
    async def test_marketplace_import_close_error_handling(
        self, db, test_user, test_vendor
    ):
        """Test handling when session close fails"""
        # Create import job
        job = MarketplaceImportJob(
            status="pending",
            source_url="http://example.com/test.csv",
            marketplace="TestMarket",
            vendor_id=test_vendor.id,
            user_id=test_user.id,
            language="en",
        )
        db.add(job)
        db.commit()
        db.refresh(job)

        # Store the job ID before it becomes detached
        job_id = job.id

        # Create a mock session that will fail on close
        mock_session = MagicMock()
        mock_session.query.return_value.filter.return_value.first.side_effect = [
            job,  # First call returns the job
            test_vendor,  # Second call returns the vendor
        ]
        mock_session.commit = MagicMock()
        mock_session.close.side_effect = Exception("Simulated close failure")

        with (
            patch("app.tasks.background_tasks.CSVProcessor") as mock_processor,
            patch(
                "app.tasks.background_tasks.SessionLocal", return_value=mock_session
            ),
        ):
            mock_instance = mock_processor.return_value
            mock_instance.process_marketplace_csv_from_url = AsyncMock(
                return_value={
                    "imported": 10,
                    "updated": 5,
                    "total_processed": 15,
                    "errors": 0,
                }
            )

            # Run background task - should handle close error gracefully
            await process_marketplace_import(
                job_id,
                "http://example.com/test.csv",
                "TestMarket",
                test_vendor.id,
                1000,
            )

            # The close method should have been called
            mock_session.close.assert_called_once()
