# tests/unit/services/test_onboarding_service.py
"""
Unit tests for OnboardingService.

Tests cover:
- Onboarding CRUD operations
- Step completion logic
- Step order validation
- Order sync progress tracking
- Admin skip functionality
"""

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

import pytest

from app.modules.marketplace.models import (
    OnboardingStatus,
    OnboardingStep,
    StoreOnboarding,
)
from app.modules.marketplace.services.onboarding_service import OnboardingService


@pytest.mark.unit
@pytest.mark.service
class TestOnboardingServiceCRUD:
    """Test CRUD operations"""

    def test_get_onboarding_returns_existing(self, db, test_store):
        """Test get_onboarding returns existing record"""
        # Create onboarding
        onboarding = StoreOnboarding(
            store_id=test_store.id,
            status=OnboardingStatus.IN_PROGRESS.value,
            current_step=OnboardingStep.LETZSHOP_API.value,
        )
        db.add(onboarding)
        db.commit()

        service = OnboardingService(db)
        result = service.get_onboarding(test_store.id)

        assert result is not None
        assert result.id == onboarding.id
        assert result.store_id == test_store.id

    def test_get_onboarding_returns_none_if_missing(self, db):
        """Test get_onboarding returns None if no record"""
        service = OnboardingService(db)
        result = service.get_onboarding(99999)
        assert result is None

    def test_get_onboarding_or_raise_raises_exception(self, db):
        """Test get_onboarding_or_raise raises OnboardingNotFoundException"""
        from app.modules.marketplace.exceptions import OnboardingNotFoundException

        service = OnboardingService(db)
        with pytest.raises(OnboardingNotFoundException):
            service.get_onboarding_or_raise(99999)

    def test_create_onboarding_creates_new(self, db, test_store):
        """Test create_onboarding creates new record"""
        service = OnboardingService(db)
        result = service.create_onboarding(test_store.id)

        assert result is not None
        assert result.store_id == test_store.id
        assert result.status == OnboardingStatus.NOT_STARTED.value
        assert result.current_step == OnboardingStep.MERCHANT_PROFILE.value

    def test_create_onboarding_returns_existing(self, db, test_store):
        """Test create_onboarding returns existing record if already exists"""
        # Create existing
        existing = StoreOnboarding(
            store_id=test_store.id,
            status=OnboardingStatus.IN_PROGRESS.value,
            current_step=OnboardingStep.LETZSHOP_API.value,
        )
        db.add(existing)
        db.commit()

        service = OnboardingService(db)
        result = service.create_onboarding(test_store.id)

        assert result.id == existing.id
        assert result.status == OnboardingStatus.IN_PROGRESS.value

    def test_get_or_create_creates_if_missing(self, db, test_store):
        """Test get_or_create_onboarding creates if missing"""
        service = OnboardingService(db)
        result = service.get_or_create_onboarding(test_store.id)

        assert result is not None
        assert result.store_id == test_store.id

    def test_is_completed_returns_false_if_no_record(self, db):
        """Test is_completed returns False if no record"""
        service = OnboardingService(db)
        assert service.is_completed(99999) is False

    def test_is_completed_returns_false_if_in_progress(self, db, test_store):
        """Test is_completed returns False if in progress"""
        onboarding = StoreOnboarding(
            store_id=test_store.id,
            status=OnboardingStatus.IN_PROGRESS.value,
        )
        db.add(onboarding)
        db.commit()

        service = OnboardingService(db)
        assert service.is_completed(test_store.id) is False

    def test_is_completed_returns_true_if_completed(self, db, test_store):
        """Test is_completed returns True if completed"""
        onboarding = StoreOnboarding(
            store_id=test_store.id,
            status=OnboardingStatus.COMPLETED.value,
            step_merchant_profile_completed=True,
            step_letzshop_api_completed=True,
            step_product_import_completed=True,
            step_order_sync_completed=True,
        )
        db.add(onboarding)
        db.commit()

        service = OnboardingService(db)
        assert service.is_completed(test_store.id) is True


@pytest.mark.unit
@pytest.mark.service
class TestOnboardingServiceStatusResponse:
    """Test status response generation"""

    def test_get_status_response_structure(self, db, test_store):
        """Test status response has correct structure"""
        service = OnboardingService(db)
        result = service.get_status_response(test_store.id)

        assert "id" in result
        assert "store_id" in result
        assert "status" in result
        assert "current_step" in result
        assert "merchant_profile" in result
        assert "letzshop_api" in result
        assert "product_import" in result
        assert "order_sync" in result
        assert "completion_percentage" in result
        assert "completed_steps_count" in result
        assert "total_steps" in result
        assert "is_completed" in result

    def test_get_status_response_step_details(self, db, test_store):
        """Test status response has step details"""
        onboarding = StoreOnboarding(
            store_id=test_store.id,
            status=OnboardingStatus.IN_PROGRESS.value,
            step_merchant_profile_completed=True,
            step_merchant_profile_data={"merchant_name": "Test"},
        )
        db.add(onboarding)
        db.commit()

        service = OnboardingService(db)
        result = service.get_status_response(test_store.id)

        assert result["merchant_profile"]["completed"] is True
        assert result["merchant_profile"]["data"]["merchant_name"] == "Test"


@pytest.mark.unit
@pytest.mark.service
class TestOnboardingServiceStep1:
    """Test Step 1: Merchant Profile"""

    def test_get_merchant_profile_data_empty_store(self, db):
        """Test get_merchant_profile_data returns empty for non-existent store"""
        service = OnboardingService(db)
        result = service.get_merchant_profile_data(99999)
        assert result == {}

    def test_get_merchant_profile_data_with_data(self, db, test_store):
        """Test get_merchant_profile_data returns store data"""
        test_store.name = "Test Brand"
        test_store.description = "Test Description"
        test_store.default_language = "fr"
        db.commit()

        service = OnboardingService(db)
        result = service.get_merchant_profile_data(test_store.id)

        assert result["brand_name"] == "Test Brand"
        assert result["description"] == "Test Description"
        assert result["default_language"] == "fr"

    def test_complete_merchant_profile_updates_status(self, db, test_store):
        """Test complete_merchant_profile updates onboarding status"""
        service = OnboardingService(db)
        result = service.complete_merchant_profile(
            store_id=test_store.id,
            merchant_name="Test Merchant",
            brand_name="Test Brand",
            default_language="en",
            dashboard_language="en",
        )
        db.commit()

        assert result["success"] is True
        assert result["step_completed"] is True
        assert result["next_step"] == OnboardingStep.LETZSHOP_API.value

        # Verify onboarding updated
        onboarding = service.get_onboarding(test_store.id)
        assert onboarding.status == OnboardingStatus.IN_PROGRESS.value
        assert onboarding.step_merchant_profile_completed is True

    def test_complete_merchant_profile_raises_for_missing_store(self, db):
        """Test complete_merchant_profile raises for non-existent store"""
        from app.modules.tenancy.exceptions import StoreNotFoundException

        service = OnboardingService(db)

        # Use a store_id that doesn't exist
        # The service should check store exists before doing anything
        non_existent_store_id = 999999

        with pytest.raises(StoreNotFoundException):
            service.complete_merchant_profile(
                store_id=non_existent_store_id,
                default_language="en",
                dashboard_language="en",
            )


@pytest.mark.unit
@pytest.mark.service
class TestOnboardingServiceStep2:
    """Test Step 2: Letzshop API Configuration"""

    def test_test_letzshop_api_returns_result(self, db, test_store):
        """Test test_letzshop_api returns connection test result"""
        with patch(
            "app.modules.marketplace.services.onboarding_service.LetzshopCredentialsService"
        ) as mock_service:
            mock_instance = MagicMock()
            mock_instance.test_api_key.return_value = (True, 150.0, None)
            mock_service.return_value = mock_instance

            service = OnboardingService(db)
            result = service.test_letzshop_api(
                api_key="test_key",
                shop_slug="test-shop",
            )

            assert result["success"] is True
            assert "150" in result["message"]

    def test_test_letzshop_api_returns_error(self, db, test_store):
        """Test test_letzshop_api returns error on failure"""
        with patch(
            "app.modules.marketplace.services.onboarding_service.LetzshopCredentialsService"
        ) as mock_service:
            mock_instance = MagicMock()
            mock_instance.test_api_key.return_value = (False, None, "Invalid API key")
            mock_service.return_value = mock_instance

            service = OnboardingService(db)
            result = service.test_letzshop_api(
                api_key="invalid_key",
                shop_slug="test-shop",
            )

            assert result["success"] is False
            assert "Invalid API key" in result["message"]

    def test_complete_letzshop_api_requires_step1(self, db, test_store):
        """Test complete_letzshop_api requires step 1 complete"""
        from app.modules.marketplace.exceptions import OnboardingStepOrderException

        # Create onboarding with step 1 not complete
        onboarding = StoreOnboarding(
            store_id=test_store.id,
            status=OnboardingStatus.NOT_STARTED.value,
            current_step=OnboardingStep.MERCHANT_PROFILE.value,
            step_merchant_profile_completed=False,
        )
        db.add(onboarding)
        db.commit()

        service = OnboardingService(db)
        with pytest.raises(OnboardingStepOrderException):
            service.complete_letzshop_api(
                store_id=test_store.id,
                api_key="test_key",
                shop_slug="test-shop",
            )


@pytest.mark.unit
@pytest.mark.service
class TestOnboardingServiceStep3:
    """Test Step 3: Product Import Configuration"""

    def test_get_product_import_config_empty(self, db):
        """Test get_product_import_config returns empty for non-existent store"""
        service = OnboardingService(db)
        result = service.get_product_import_config(99999)
        assert result == {}

    def test_get_product_import_config_with_data(self, db, test_store):
        """Test get_product_import_config returns store CSV settings"""
        test_store.letzshop_csv_url_fr = "https://example.com/fr.csv"
        test_store.letzshop_default_tax_rate = 17
        db.commit()

        service = OnboardingService(db)
        result = service.get_product_import_config(test_store.id)

        assert result["csv_url_fr"] == "https://example.com/fr.csv"
        assert result["default_tax_rate"] == 17

    def test_complete_product_import_requires_csv_url(self, db, test_store):
        """Test complete_product_import requires at least one CSV URL"""
        from app.modules.marketplace.exceptions import OnboardingCsvUrlRequiredException

        # Create onboarding with steps 1 and 2 complete
        onboarding = StoreOnboarding(
            store_id=test_store.id,
            status=OnboardingStatus.IN_PROGRESS.value,
            current_step=OnboardingStep.PRODUCT_IMPORT.value,
            step_merchant_profile_completed=True,
            step_letzshop_api_completed=True,
        )
        db.add(onboarding)
        db.commit()

        service = OnboardingService(db)
        with pytest.raises(OnboardingCsvUrlRequiredException):
            service.complete_product_import(
                store_id=test_store.id,
                # No CSV URLs provided
            )

    def test_complete_product_import_success(self, db, test_store):
        """Test complete_product_import saves settings"""
        # Create onboarding with steps 1 and 2 complete
        onboarding = StoreOnboarding(
            store_id=test_store.id,
            status=OnboardingStatus.IN_PROGRESS.value,
            current_step=OnboardingStep.PRODUCT_IMPORT.value,
            step_merchant_profile_completed=True,
            step_letzshop_api_completed=True,
        )
        db.add(onboarding)
        db.commit()

        service = OnboardingService(db)
        result = service.complete_product_import(
            store_id=test_store.id,
            csv_url_fr="https://example.com/fr.csv",
            default_tax_rate=17,
            delivery_method="package_delivery",
            preorder_days=2,
        )
        db.commit()

        assert result["success"] is True
        assert result["csv_urls_configured"] == 1

        # Verify store updated
        db.refresh(test_store)
        assert test_store.letzshop_csv_url_fr == "https://example.com/fr.csv"
        assert test_store.letzshop_default_tax_rate == 17


@pytest.mark.unit
@pytest.mark.service
class TestOnboardingServiceStep4:
    """Test Step 4: Order Sync"""

    def test_trigger_order_sync_creates_job(self, db, test_store, test_user):
        """Test trigger_order_sync creates import job"""
        # Create onboarding with steps 1-3 complete
        onboarding = StoreOnboarding(
            store_id=test_store.id,
            status=OnboardingStatus.IN_PROGRESS.value,
            current_step=OnboardingStep.ORDER_SYNC.value,
            step_merchant_profile_completed=True,
            step_letzshop_api_completed=True,
            step_product_import_completed=True,
        )
        db.add(onboarding)
        db.commit()

        with patch(
            "app.modules.marketplace.services.onboarding_service.LetzshopOrderService"
        ) as mock_service:
            mock_instance = MagicMock()
            mock_instance.get_running_historical_import_job.return_value = None
            mock_job = MagicMock()
            mock_job.id = 123
            mock_instance.create_historical_import_job.return_value = mock_job
            mock_service.return_value = mock_instance

            service = OnboardingService(db)
            result = service.trigger_order_sync(
                store_id=test_store.id,
                user_id=test_user.id,
                days_back=90,
            )

            assert result["success"] is True
            assert result["job_id"] == 123

    def test_trigger_order_sync_returns_existing_job(self, db, test_store, test_user):
        """Test trigger_order_sync returns existing job if running"""
        # Create onboarding with steps 1-3 complete
        onboarding = StoreOnboarding(
            store_id=test_store.id,
            status=OnboardingStatus.IN_PROGRESS.value,
            current_step=OnboardingStep.ORDER_SYNC.value,
            step_merchant_profile_completed=True,
            step_letzshop_api_completed=True,
            step_product_import_completed=True,
        )
        db.add(onboarding)
        db.commit()

        with patch(
            "app.modules.marketplace.services.onboarding_service.LetzshopOrderService"
        ) as mock_service:
            mock_instance = MagicMock()
            existing_job = MagicMock()
            existing_job.id = 456
            mock_instance.get_running_historical_import_job.return_value = existing_job
            mock_service.return_value = mock_instance

            service = OnboardingService(db)
            result = service.trigger_order_sync(
                store_id=test_store.id,
                user_id=test_user.id,
            )

            assert result["success"] is True
            assert result["job_id"] == 456
            assert "already running" in result["message"]

    def test_get_order_sync_progress_not_found(self, db, test_store):
        """Test get_order_sync_progress for non-existent job"""
        with patch(
            "app.modules.marketplace.services.onboarding_service.LetzshopOrderService"
        ) as mock_service:
            mock_instance = MagicMock()
            mock_instance.get_historical_import_job_by_id.return_value = None
            mock_service.return_value = mock_instance

            service = OnboardingService(db)
            result = service.get_order_sync_progress(
                store_id=test_store.id,
                job_id=99999,
            )

            assert result["status"] == "not_found"
            assert result["progress_percentage"] == 0

    def test_get_order_sync_progress_completed(self, db, test_store):
        """Test get_order_sync_progress for completed job"""
        with patch(
            "app.modules.marketplace.services.onboarding_service.LetzshopOrderService"
        ) as mock_service:
            mock_instance = MagicMock()
            mock_job = MagicMock()
            mock_job.id = 123
            mock_job.status = "completed"
            mock_job.current_phase = "complete"
            mock_job.orders_imported = 50
            mock_job.shipments_fetched = 50
            mock_job.orders_processed = 50
            mock_job.products_matched = 100
            mock_job.started_at = datetime.now(UTC)
            mock_job.completed_at = datetime.now(UTC)
            mock_job.error_message = None
            mock_instance.get_historical_import_job_by_id.return_value = mock_job
            mock_service.return_value = mock_instance

            service = OnboardingService(db)
            result = service.get_order_sync_progress(
                store_id=test_store.id,
                job_id=123,
            )

            assert result["status"] == "completed"
            assert result["progress_percentage"] == 100
            assert result["orders_imported"] == 50

    def test_get_order_sync_progress_processing(self, db, test_store):
        """Test get_order_sync_progress for processing job"""
        with patch(
            "app.modules.marketplace.services.onboarding_service.LetzshopOrderService"
        ) as mock_service:
            mock_instance = MagicMock()
            mock_job = MagicMock()
            mock_job.id = 123
            mock_job.status = "processing"
            mock_job.current_phase = "orders"
            mock_job.orders_imported = 25
            mock_job.shipments_fetched = 50
            mock_job.orders_processed = 25
            mock_job.products_matched = 50
            mock_job.started_at = datetime.now(UTC)
            mock_job.completed_at = None
            mock_job.error_message = None
            mock_job.total_pages = None
            mock_job.current_page = None
            mock_instance.get_historical_import_job_by_id.return_value = mock_job
            mock_service.return_value = mock_instance

            service = OnboardingService(db)
            result = service.get_order_sync_progress(
                store_id=test_store.id,
                job_id=123,
            )

            assert result["status"] == "processing"
            assert result["progress_percentage"] == 50  # 25/50
            assert result["current_phase"] == "orders"

    def test_complete_order_sync_raises_for_missing_job(self, db, test_store):
        """Test complete_order_sync raises for non-existent job"""
        from app.modules.marketplace.exceptions import (
            OnboardingSyncJobNotFoundException,
        )

        onboarding = StoreOnboarding(
            store_id=test_store.id,
            status=OnboardingStatus.IN_PROGRESS.value,
        )
        db.add(onboarding)
        db.commit()

        with patch(
            "app.modules.marketplace.services.onboarding_service.LetzshopOrderService"
        ) as mock_service:
            mock_instance = MagicMock()
            mock_instance.get_historical_import_job_by_id.return_value = None
            mock_service.return_value = mock_instance

            service = OnboardingService(db)
            with pytest.raises(OnboardingSyncJobNotFoundException):
                service.complete_order_sync(
                    store_id=test_store.id,
                    job_id=99999,
                )

    def test_complete_order_sync_raises_if_not_complete(self, db, test_store):
        """Test complete_order_sync raises if job still running"""
        from app.modules.marketplace.exceptions import (
            OnboardingSyncNotCompleteException,
        )

        onboarding = StoreOnboarding(
            store_id=test_store.id,
            status=OnboardingStatus.IN_PROGRESS.value,
        )
        db.add(onboarding)
        db.commit()

        with patch(
            "app.modules.marketplace.services.onboarding_service.LetzshopOrderService"
        ) as mock_service:
            mock_instance = MagicMock()
            mock_job = MagicMock()
            mock_job.status = "processing"
            mock_instance.get_historical_import_job_by_id.return_value = mock_job
            mock_service.return_value = mock_instance

            service = OnboardingService(db)
            with pytest.raises(OnboardingSyncNotCompleteException):
                service.complete_order_sync(
                    store_id=test_store.id,
                    job_id=123,
                )


@pytest.mark.unit
@pytest.mark.service
class TestOnboardingServiceAdminSkip:
    """Test admin skip functionality"""

    def test_skip_onboarding_success(self, db, test_store, test_admin):
        """Test skip_onboarding marks onboarding as skipped"""
        service = OnboardingService(db)
        result = service.skip_onboarding(
            store_id=test_store.id,
            admin_user_id=test_admin.id,
            reason="Manual setup required",
        )
        db.commit()

        assert result["success"] is True

        # Verify onboarding updated
        onboarding = service.get_onboarding(test_store.id)
        assert onboarding.skipped_by_admin is True
        assert onboarding.skipped_reason == "Manual setup required"
        assert onboarding.status == OnboardingStatus.SKIPPED.value
