# app/modules/core/tests/unit/test_onboarding_aggregator.py
"""Unit tests for OnboardingAggregatorService."""

from unittest.mock import MagicMock, patch

import pytest

from app.modules.contracts.onboarding import (
    OnboardingStepDefinition,
    OnboardingStepStatus,
)
from app.modules.core.services.onboarding_aggregator import OnboardingAggregatorService


def _make_step(key: str, order: int = 100, category: str = "test") -> OnboardingStepDefinition:
    return OnboardingStepDefinition(
        key=key,
        title_key=f"{key}.title",
        description_key=f"{key}.desc",
        icon="circle",
        route_template="/store/{store_code}/test",
        order=order,
        category=category,
    )


@pytest.mark.unit
@pytest.mark.core
class TestOnboardingAggregatorSteps:
    """Tests for OnboardingAggregatorService.get_onboarding_steps."""

    def setup_method(self):
        self.service = OnboardingAggregatorService()

    def test_aggregates_steps_from_multiple_providers(self, db):
        """Collects steps from all enabled module providers."""
        step_a = _make_step("tenancy.customize", order=100, category="tenancy")
        step_b = _make_step("marketplace.connect", order=200, category="marketplace")

        provider_a = MagicMock()
        provider_a.onboarding_category = "tenancy"
        provider_a.get_onboarding_steps.return_value = [step_a]
        provider_a.is_step_completed.return_value = True

        provider_b = MagicMock()
        provider_b.onboarding_category = "marketplace"
        provider_b.get_onboarding_steps.return_value = [step_b]
        provider_b.is_step_completed.return_value = False

        module_a = MagicMock()
        module_a.code = "tenancy"
        module_b = MagicMock()
        module_b.code = "marketplace"

        with patch.object(
            self.service,
            "_get_enabled_providers",
            return_value=[(module_a, provider_a), (module_b, provider_b)],
        ):
            steps = self.service.get_onboarding_steps(db, store_id=1, platform_id=1)

        assert len(steps) == 2
        assert steps[0].step.key == "tenancy.customize"
        assert steps[0].completed is True
        assert steps[1].step.key == "marketplace.connect"
        assert steps[1].completed is False

    def test_steps_sorted_by_order(self, db):
        """Steps are returned sorted by order regardless of provider order."""
        step_high = _make_step("loyalty.program", order=300)
        step_low = _make_step("tenancy.customize", order=100)

        provider_high = MagicMock()
        provider_high.onboarding_category = "loyalty"
        provider_high.get_onboarding_steps.return_value = [step_high]
        provider_high.is_step_completed.return_value = False

        provider_low = MagicMock()
        provider_low.onboarding_category = "tenancy"
        provider_low.get_onboarding_steps.return_value = [step_low]
        provider_low.is_step_completed.return_value = False

        module_high = MagicMock()
        module_high.code = "loyalty"
        module_low = MagicMock()
        module_low.code = "tenancy"

        # Provide high-order first
        with patch.object(
            self.service,
            "_get_enabled_providers",
            return_value=[(module_high, provider_high), (module_low, provider_low)],
        ):
            steps = self.service.get_onboarding_steps(db, store_id=1, platform_id=1)

        assert steps[0].step.key == "tenancy.customize"
        assert steps[1].step.key == "loyalty.program"

    def test_empty_when_no_providers(self, db):
        """Returns empty list when no modules have onboarding providers."""
        with patch.object(self.service, "_get_enabled_providers", return_value=[]):
            steps = self.service.get_onboarding_steps(db, store_id=1, platform_id=1)

        assert steps == []

    def test_handles_provider_error_gracefully(self, db):
        """Skips a provider that raises an exception."""
        provider = MagicMock()
        provider.onboarding_category = "broken"
        provider.get_onboarding_steps.side_effect = RuntimeError("DB error")
        module = MagicMock()
        module.code = "broken"

        with patch.object(
            self.service, "_get_enabled_providers", return_value=[(module, provider)]
        ):
            steps = self.service.get_onboarding_steps(db, store_id=1, platform_id=1)

        assert steps == []

    def test_handles_step_completion_check_error(self, db):
        """Marks step as incomplete when completion check raises."""
        step = _make_step("test.step")
        provider = MagicMock()
        provider.onboarding_category = "test"
        provider.get_onboarding_steps.return_value = [step]
        provider.is_step_completed.side_effect = RuntimeError("query error")
        module = MagicMock()
        module.code = "test"

        with patch.object(
            self.service, "_get_enabled_providers", return_value=[(module, provider)]
        ):
            steps = self.service.get_onboarding_steps(db, store_id=1, platform_id=1)

        assert len(steps) == 1
        assert steps[0].completed is False


@pytest.mark.unit
@pytest.mark.core
class TestOnboardingAggregatorSummary:
    """Tests for OnboardingAggregatorService.get_onboarding_summary."""

    def setup_method(self):
        self.service = OnboardingAggregatorService()

    def test_summary_with_mixed_completion(self, db):
        """Returns correct progress for partially completed onboarding."""
        step_a = _make_step("tenancy.customize", order=100, category="tenancy")
        step_b = _make_step("marketplace.connect", order=200, category="marketplace")
        step_c = _make_step("marketplace.import", order=210, category="marketplace")

        provider_a = MagicMock()
        provider_a.onboarding_category = "tenancy"
        provider_a.get_onboarding_steps.return_value = [step_a]
        provider_a.is_step_completed.return_value = True

        provider_b = MagicMock()
        provider_b.onboarding_category = "marketplace"
        provider_b.get_onboarding_steps.return_value = [step_b, step_c]
        provider_b.is_step_completed.return_value = False

        module_a = MagicMock()
        module_a.code = "tenancy"
        module_b = MagicMock()
        module_b.code = "marketplace"

        with patch.object(
            self.service,
            "_get_enabled_providers",
            return_value=[(module_a, provider_a), (module_b, provider_b)],
        ):
            summary = self.service.get_onboarding_summary(
                db, store_id=1, platform_id=1, store_code="MYSTORE"
            )

        assert summary["total_steps"] == 3
        assert summary["completed_steps"] == 1
        assert summary["progress_percentage"] == 33
        assert summary["all_completed"] is False
        assert len(summary["steps"]) == 3

    def test_summary_all_completed(self, db):
        """Returns all_completed=True when every step is done."""
        step = _make_step("tenancy.customize")
        provider = MagicMock()
        provider.onboarding_category = "tenancy"
        provider.get_onboarding_steps.return_value = [step]
        provider.is_step_completed.return_value = True
        module = MagicMock()
        module.code = "tenancy"

        with patch.object(
            self.service, "_get_enabled_providers", return_value=[(module, provider)]
        ):
            summary = self.service.get_onboarding_summary(
                db, store_id=1, platform_id=1, store_code="TEST"
            )

        assert summary["all_completed"] is True
        assert summary["progress_percentage"] == 100

    def test_summary_empty_providers(self, db):
        """Returns zeros and all_completed=False when no providers."""
        with patch.object(self.service, "_get_enabled_providers", return_value=[]):
            summary = self.service.get_onboarding_summary(
                db, store_id=1, platform_id=1, store_code="TEST"
            )

        assert summary["total_steps"] == 0
        assert summary["completed_steps"] == 0
        assert summary["progress_percentage"] == 0
        assert summary["all_completed"] is False

    def test_summary_replaces_store_code_in_routes(self, db):
        """Route templates have {store_code} replaced with actual store code."""
        step = OnboardingStepDefinition(
            key="test.step",
            title_key="test.title",
            description_key="test.desc",
            icon="circle",
            route_template="/store/{store_code}/settings",
            order=100,
            category="test",
        )
        provider = MagicMock()
        provider.onboarding_category = "test"
        provider.get_onboarding_steps.return_value = [step]
        provider.is_step_completed.return_value = False
        module = MagicMock()
        module.code = "test"

        with patch.object(
            self.service, "_get_enabled_providers", return_value=[(module, provider)]
        ):
            summary = self.service.get_onboarding_summary(
                db, store_id=1, platform_id=1, store_code="MY_STORE"
            )

        assert summary["steps"][0]["route"] == "/store/MY_STORE/settings"

    def test_summary_step_data_includes_all_fields(self, db):
        """Each step in summary includes key, title_key, description_key, icon, route, completed, category."""
        step = OnboardingStepDefinition(
            key="test.step",
            title_key="test.title",
            description_key="test.desc",
            icon="settings",
            route_template="/store/{store_code}/test",
            order=100,
            category="test_cat",
        )
        provider = MagicMock()
        provider.onboarding_category = "test"
        provider.get_onboarding_steps.return_value = [step]
        provider.is_step_completed.return_value = True
        module = MagicMock()
        module.code = "test"

        with patch.object(
            self.service, "_get_enabled_providers", return_value=[(module, provider)]
        ):
            summary = self.service.get_onboarding_summary(
                db, store_id=1, platform_id=1, store_code="S1"
            )

        step_data = summary["steps"][0]
        assert step_data["key"] == "test.step"
        assert step_data["title_key"] == "test.title"
        assert step_data["description_key"] == "test.desc"
        assert step_data["icon"] == "settings"
        assert step_data["route"] == "/store/S1/test"
        assert step_data["completed"] is True
        assert step_data["category"] == "test_cat"
