# tests/integration/tasks/test_letzshop_tasks.py
"""Integration tests for Letzshop background tasks."""

from datetime import UTC, datetime
from unittest.mock import MagicMock, patch

import pytest

from app.modules.marketplace.services.letzshop import LetzshopClientError
from app.modules.marketplace.tasks import process_historical_import
from app.modules.marketplace.models import LetzshopHistoricalImportJob


@pytest.fixture
def historical_import_job(db, test_vendor, test_user):
    """Create a test historical import job."""
    job = LetzshopHistoricalImportJob(
        vendor_id=test_vendor.id,
        user_id=test_user.id,
        status="pending",
    )
    db.add(job)
    db.commit()
    db.refresh(job)
    return job


@pytest.mark.integration
@pytest.mark.database
@pytest.mark.letzshop
class TestHistoricalImportTask:
    """Test historical import background task."""

    def test_job_not_found(self, db, test_vendor):
        """Test handling when job doesn't exist."""
        with patch("app.tasks.letzshop_tasks.SessionLocal", return_value=db):
            # Should not raise an exception
            process_historical_import(job_id=99999, vendor_id=test_vendor.id)

    def test_successful_import(self, db, test_vendor, historical_import_job):
        """Test successful historical import with both phases."""
        job_id = historical_import_job.id

        # Mock the services
        mock_client = MagicMock()
        mock_client.__enter__ = MagicMock(return_value=mock_client)
        mock_client.__exit__ = MagicMock(return_value=False)
        mock_client.get_all_shipments_paginated.side_effect = [
            # First call: confirmed shipments
            [{"id": "1", "state": "confirmed"}, {"id": "2", "state": "confirmed"}],
            # Second call: unconfirmed shipments
            [{"id": "3", "state": "unconfirmed"}],
        ]

        mock_creds_service = MagicMock()
        mock_creds_service.create_client.return_value = mock_client
        mock_creds_service.update_sync_status = MagicMock()

        mock_order_service = MagicMock()
        mock_order_service.import_historical_shipments.side_effect = [
            # First call: confirmed stats
            {
                "total": 2,
                "imported": 2,
                "updated": 0,
                "skipped": 0,
                "products_matched": 5,
                "products_not_found": 1,
            },
            # Second call: unconfirmed stats
            {
                "total": 1,
                "imported": 1,
                "updated": 0,
                "skipped": 0,
                "products_matched": 2,
                "products_not_found": 0,
            },
        ]

        with (
            patch("app.tasks.letzshop_tasks.SessionLocal", return_value=db),
            patch(
                "app.tasks.letzshop_tasks._get_credentials_service",
                return_value=mock_creds_service,
            ),
            patch(
                "app.tasks.letzshop_tasks._get_order_service",
                return_value=mock_order_service,
            ),
        ):
            process_historical_import(job_id=job_id, vendor_id=test_vendor.id)

        # Verify job was updated
        updated_job = (
            db.query(LetzshopHistoricalImportJob)
            .filter(LetzshopHistoricalImportJob.id == job_id)
            .first()
        )
        assert updated_job.status == "completed"
        assert updated_job.started_at is not None
        assert updated_job.completed_at is not None
        assert updated_job.confirmed_stats["imported"] == 2
        assert updated_job.declined_stats["imported"] == 1
        assert updated_job.products_matched == 7
        assert updated_job.products_not_found == 1

        # Verify sync status was updated
        mock_creds_service.update_sync_status.assert_called_with(
            test_vendor.id, "success", None
        )

    def test_letzshop_client_error(self, db, test_vendor, historical_import_job):
        """Test handling Letzshop API errors."""
        job_id = historical_import_job.id

        mock_creds_service = MagicMock()
        mock_creds_service.create_client.side_effect = LetzshopClientError(
            "API connection failed"
        )
        mock_creds_service.update_sync_status = MagicMock()

        mock_order_service = MagicMock()
        mock_order_service.get_vendor.return_value = test_vendor

        with (
            patch("app.tasks.letzshop_tasks.SessionLocal", return_value=db),
            patch(
                "app.tasks.letzshop_tasks._get_credentials_service",
                return_value=mock_creds_service,
            ),
            patch(
                "app.tasks.letzshop_tasks._get_order_service",
                return_value=mock_order_service,
            ),
            patch(
                "app.tasks.letzshop_tasks.admin_notification_service"
            ) as mock_notify,
        ):
            process_historical_import(job_id=job_id, vendor_id=test_vendor.id)

        # Verify job failed
        updated_job = (
            db.query(LetzshopHistoricalImportJob)
            .filter(LetzshopHistoricalImportJob.id == job_id)
            .first()
        )
        assert updated_job.status == "failed"
        assert "API connection failed" in updated_job.error_message
        assert updated_job.completed_at is not None

        # Verify sync status was updated to failed
        mock_creds_service.update_sync_status.assert_called_with(
            test_vendor.id, "failed", "API connection failed"
        )

    def test_unexpected_error(self, db, test_vendor, historical_import_job):
        """Test handling unexpected errors."""
        job_id = historical_import_job.id

        mock_creds_service = MagicMock()
        mock_creds_service.create_client.side_effect = RuntimeError("Unexpected error")

        mock_order_service = MagicMock()
        mock_order_service.get_vendor.return_value = test_vendor

        with (
            patch("app.tasks.letzshop_tasks.SessionLocal", return_value=db),
            patch(
                "app.tasks.letzshop_tasks._get_credentials_service",
                return_value=mock_creds_service,
            ),
            patch(
                "app.tasks.letzshop_tasks._get_order_service",
                return_value=mock_order_service,
            ),
            patch(
                "app.tasks.letzshop_tasks.admin_notification_service"
            ) as mock_notify,
        ):
            process_historical_import(job_id=job_id, vendor_id=test_vendor.id)

        # Verify job failed
        updated_job = (
            db.query(LetzshopHistoricalImportJob)
            .filter(LetzshopHistoricalImportJob.id == job_id)
            .first()
        )
        assert updated_job.status == "failed"
        assert "Unexpected error" in updated_job.error_message

        # Verify critical error notification was sent
        mock_notify.notify_critical_error.assert_called_once()

    def test_progress_tracking(self, db, test_vendor, historical_import_job):
        """Test that progress is tracked correctly during import."""
        job_id = historical_import_job.id
        progress_updates = []

        # Create a mock client that tracks progress calls
        mock_client = MagicMock()
        mock_client.__enter__ = MagicMock(return_value=mock_client)
        mock_client.__exit__ = MagicMock(return_value=False)

        def track_fetch_progress(*args, **kwargs):
            # Simulate fetching shipments and call progress callback
            progress_callback = kwargs.get("progress_callback")
            if progress_callback:
                progress_callback(1, 10)
                progress_callback(2, 20)
            return [{"id": str(i)} for i in range(20)]

        mock_client.get_all_shipments_paginated.side_effect = [
            track_fetch_progress(state="confirmed", page_size=50, progress_callback=None),
            [],  # Empty unconfirmed
        ]

        mock_creds_service = MagicMock()
        mock_creds_service.create_client.return_value = mock_client
        mock_creds_service.update_sync_status = MagicMock()

        mock_order_service = MagicMock()
        mock_order_service.import_historical_shipments.return_value = {
            "total": 20,
            "imported": 18,
            "updated": 2,
            "skipped": 0,
            "products_matched": 50,
            "products_not_found": 5,
        }

        with (
            patch("app.tasks.letzshop_tasks.SessionLocal", return_value=db),
            patch(
                "app.tasks.letzshop_tasks._get_credentials_service",
                return_value=mock_creds_service,
            ),
            patch(
                "app.tasks.letzshop_tasks._get_order_service",
                return_value=mock_order_service,
            ),
        ):
            process_historical_import(job_id=job_id, vendor_id=test_vendor.id)

        # Verify final job state
        updated_job = (
            db.query(LetzshopHistoricalImportJob)
            .filter(LetzshopHistoricalImportJob.id == job_id)
            .first()
        )
        assert updated_job.status == "completed"

    def test_commit_error_in_exception_handler(
        self, db, test_vendor, historical_import_job
    ):
        """Test handling when commit fails during exception handling."""
        job_id = historical_import_job.id

        # Create a mock session that fails on the second commit
        mock_session = MagicMock()
        mock_session.query.return_value.filter.return_value.first.return_value = (
            historical_import_job
        )
        mock_session.commit.side_effect = [
            None,  # First commit (status update) succeeds
            Exception("Commit failed"),  # Second commit fails
        ]
        mock_session.rollback = MagicMock()
        mock_session.close = MagicMock()

        mock_creds_service = MagicMock()
        mock_creds_service.create_client.side_effect = RuntimeError("Test error")

        mock_order_service = MagicMock()
        mock_order_service.get_vendor.return_value = test_vendor

        with (
            patch("app.tasks.letzshop_tasks.SessionLocal", return_value=mock_session),
            patch(
                "app.tasks.letzshop_tasks._get_credentials_service",
                return_value=mock_creds_service,
            ),
            patch(
                "app.tasks.letzshop_tasks._get_order_service",
                return_value=mock_order_service,
            ),
            patch("app.tasks.letzshop_tasks.admin_notification_service"),
        ):
            # Should not raise
            process_historical_import(job_id=job_id, vendor_id=test_vendor.id)

        # Verify rollback was called
        mock_session.rollback.assert_called()

    def test_close_error_handling(self, db, test_vendor, historical_import_job):
        """Test handling when session close fails."""
        job_id = historical_import_job.id

        # Create a mock session that fails on close
        mock_session = MagicMock()
        mock_session.query.return_value.filter.return_value.first.return_value = (
            historical_import_job
        )
        mock_session.commit = MagicMock()
        mock_session.close.side_effect = Exception("Close failed")

        mock_client = MagicMock()
        mock_client.__enter__ = MagicMock(return_value=mock_client)
        mock_client.__exit__ = MagicMock(return_value=False)
        mock_client.get_all_shipments_paginated.return_value = []

        mock_creds_service = MagicMock()
        mock_creds_service.create_client.return_value = mock_client
        mock_creds_service.update_sync_status = MagicMock()

        mock_order_service = MagicMock()
        mock_order_service.import_historical_shipments.return_value = {
            "total": 0,
            "imported": 0,
            "updated": 0,
            "skipped": 0,
            "products_matched": 0,
            "products_not_found": 0,
        }

        with (
            patch("app.tasks.letzshop_tasks.SessionLocal", return_value=mock_session),
            patch(
                "app.tasks.letzshop_tasks._get_credentials_service",
                return_value=mock_creds_service,
            ),
            patch(
                "app.tasks.letzshop_tasks._get_order_service",
                return_value=mock_order_service,
            ),
        ):
            # Should not raise
            process_historical_import(job_id=job_id, vendor_id=test_vendor.id)

        # Verify close was attempted
        mock_session.close.assert_called()
