"""
Integration Tests — Cross-Module E2E Scenarios.

تست‌های یکپارچگی بین ماژول‌های PM ↔ PPM ↔ Strategy ↔ Analytics.
"""
import uuid

import pytest
from unittest.mock import patch, MagicMock

from apps.core.event_bus.events import EventBus, DomainEvent, BaseEvent
from dataclasses import dataclass


# Custom event classes for testing
@dataclass
class ProjectCompletedEvent(DomainEvent):
    """تست ایونت تکمیل پروژه."""
    pass


@dataclass
class PortfolioCreatedEvent(DomainEvent):
    """تست ایونت ایجاد پرتفولیو."""
    pass


@pytest.fixture
def fresh_event_bus():
    """Provide EventBus with clean handlers for testing."""
    bus = EventBus()
    saved = dict(EventBus._handlers)
    EventBus._handlers = {}
    yield bus
    EventBus._handlers = saved


@pytest.fixture
def ppm_user(user_factory):
    return user_factory(email="ppm@test.com", first_name="مدیر", last_name="پرتفولیو")


# ═══════════════════════════════════════════════════
# 1. PPM ← PM Event Flow
# ═══════════════════════════════════════════════════


@pytest.mark.django_db
class TestPPMProjectEventFlow:
    """تست جریان رویدادها از PM به PPM."""

    def test_program_project_linkage_lifecycle(self, tenant, ppm_user):
        """
        سناریو: ایجاد برنامه → افزودن پروژه → تکمیل پروژه → بررسی وابستگی.
        """
        from modules.ppm.backend.infrastructure.persistence.models import (
            ProgramModel, ProgramProjectModel, CrossProjectDependencyModel,
        )

        # 1. Create a program
        program = ProgramModel.objects.create(
            tenant=tenant,
            name="برنامه تحول دیجیتال",
            code="PRG-001",
            status="active",
            manager_id=ppm_user.id,
        )
        assert program.status == "active"

        # 2. Add project references
        project_a_id = uuid.uuid4()
        project_b_id = uuid.uuid4()

        link_a = ProgramProjectModel.objects.create(
            tenant=tenant,
            program=program,
            project_id=project_a_id,
            role="member",
        )
        link_b = ProgramProjectModel.objects.create(
            tenant=tenant,
            program=program,
            project_id=project_b_id,
            role="member",
        )
        assert ProgramProjectModel.objects.filter(program=program).count() == 2

        # 3. Create cross-project dependency (FS = Finish-to-Start)
        dep = CrossProjectDependencyModel.objects.create(
            tenant=tenant,
            predecessor_project_id=project_a_id,
            successor_project_id=project_b_id,
            dependency_type="FS",
            status="active",
        )
        assert dep.status == "active"

        # 4. Resolve dependency (simulating project A completion)
        dep.status = "resolved"
        dep.save()
        dep.refresh_from_db()
        assert dep.status == "resolved"

    def test_cross_project_dependency_cascade(self, tenant):
        """
        سناریو: وابستگی زنجیره‌ای بین ۳ پروژه.
        """
        from modules.ppm.backend.infrastructure.persistence.models import (
            CrossProjectDependencyModel,
        )

        p1, p2, p3 = uuid.uuid4(), uuid.uuid4(), uuid.uuid4()

        dep1 = CrossProjectDependencyModel.objects.create(
            tenant=tenant,
            predecessor_project_id=p1,
            successor_project_id=p2,
            dependency_type="FS",
            status="active",
        )
        dep2 = CrossProjectDependencyModel.objects.create(
            tenant=tenant,
            predecessor_project_id=p2,
            successor_project_id=p3,
            dependency_type="FS",
            status="active",
        )

        # Verify chain exists
        active_deps = CrossProjectDependencyModel.objects.filter(
            tenant=tenant, status="active",
        )
        assert active_deps.count() == 2

        # Resolve first link
        dep1.status = "resolved"
        dep1.save()

        # Second still active
        dep2.refresh_from_db()
        assert dep2.status == "active"


# ═══════════════════════════════════════════════════
# 2. PPM Portfolio + Business Case Lifecycle
# ═══════════════════════════════════════════════════


@pytest.mark.django_db
class TestPortfolioBusinessCaseLifecycle:
    """تست چرخه عمر پرتفولیو و کیس تجاری."""

    def test_portfolio_with_business_case_and_scoring(self, tenant, ppm_user):
        """
        سناریو: ایجاد پرتفولیو → کیس تجاری → مدل امتیازدهی → امتیازدهی پروژه.
        """
        from modules.ppm.backend.infrastructure.persistence.models import (
            PortfolioModel, BusinessCaseModel, CashFlowProjectionModel,
            ScoringModelModel, ScoringCriterionModel, ProjectScoreModel,
        )

        # 1. Create portfolio
        portfolio = PortfolioModel.objects.create(
            tenant=tenant,
            name="پرتفولیوی IT",
            code="PF-001",
            status="active",
            owner_id=ppm_user.id,
        )

        # 2. Create business case
        project_id = uuid.uuid4()
        bc = BusinessCaseModel.objects.create(
            tenant=tenant,
            project_id=project_id,
            title="کیس تجاری پروژه ERP",
            status="draft",
            prepared_by=ppm_user,
            total_investment=50000000,
        )
        assert bc.status == "draft"

        # 3. Add cash flow projections
        CashFlowProjectionModel.objects.create(
            tenant=tenant,
            business_case=bc,
            year=1,
            investment=50000000,
            operational_cost=10000000,
            revenue=30000000,
        )

        # 4. Approve business case
        bc.status = "approved"
        bc.save()
        bc.refresh_from_db()
        assert bc.status == "approved"

        # 5. Create scoring model
        scoring = ScoringModelModel.objects.create(
            tenant=tenant,
            name="مدل امتیازدهی استاندارد",
            is_active=True,
        )
        criterion = ScoringCriterionModel.objects.create(
            tenant=tenant,
            scoring_model=scoring,
            name="بازگشت سرمایه",
            weight=40,
            scale_max=10,
        )

        # 6. Score a project
        score = ProjectScoreModel.objects.create(
            tenant=tenant,
            scoring_model=scoring,
            project_id=project_id,
            criterion=criterion,
            score=8,
            scored_by=ppm_user,
        )
        assert score.score == 8


# ═══════════════════════════════════════════════════
# 3. Strategy ← PPM Alignment
# ═══════════════════════════════════════════════════


@pytest.mark.django_db
class TestStrategyPPMAlignment:
    """تست همسویی استراتژیک بین Strategy و PPM."""

    def test_objective_portfolio_alignment_lifecycle(self, tenant, ppm_user):
        """
        سناریو: ایجاد هدف استراتژیک → همسویی با پرتفولیو → محاسبه Strategic Fit.
        """
        from modules.strategy.backend.infrastructure.persistence.models import (
            VisionModel, StrategicThemeModel, ObjectiveModel,
            ObjectivePortfolioAlignmentModel,
        )
        from modules.ppm.backend.infrastructure.persistence.models import (
            PortfolioModel,
        )

        # 1. Create vision & theme
        vision = VisionModel.objects.create(
            tenant=tenant,
            title="چشم‌انداز ۱۴۱۰",
            description="تبدیل به رهبر بازار فناوری",
            status="active",
        )
        theme = StrategicThemeModel.objects.create(
            tenant=tenant,
            vision=vision,
            name="تحول دیجیتال",
            description="محور تحول دیجیتال سازمان",
        )

        # 2. Create objective
        objective = ObjectiveModel.objects.create(
            tenant=tenant,
            theme=theme,
            title="افزایش بهره‌وری ۳۰٪",
            level="enterprise",
            status="active",
            owner_id=ppm_user.id,
        )

        # 3. Create portfolio in PPM
        portfolio = PortfolioModel.objects.create(
            tenant=tenant,
            name="پرتفولیوی تحول",
            code="PF-TRANSFORM",
            status="active",
            owner_id=ppm_user.id,
        )

        # 4. Create alignment link
        alignment = ObjectivePortfolioAlignmentModel.objects.create(
            tenant=tenant,
            objective=objective,
            portfolio_id=portfolio.id,
            alignment_level="primary",
            strategic_fit_score=85.0,
            weight=60,
        )
        assert alignment.alignment_level == "primary"
        assert float(alignment.strategic_fit_score) == 85.0

        # 5. Verify queryability
        alignments = ObjectivePortfolioAlignmentModel.objects.filter(
            tenant=tenant,
            portfolio_id=portfolio.id,
        )
        assert alignments.count() == 1

    def test_okr_with_key_results_and_checkins(self, tenant, ppm_user):
        """
        سناریو: هدف → نتیجه کلیدی → ثبت پیشرفت → محاسبه درصد.
        """
        from modules.strategy.backend.infrastructure.persistence.models import (
            VisionModel, StrategicThemeModel, ObjectiveModel,
            KeyResultModel, KeyResultCheckInModel,
        )

        vision = VisionModel.objects.create(
            tenant=tenant,
            title="چشم‌انداز",
            description="بیانیه",
            status="active",
        )
        theme = StrategicThemeModel.objects.create(
            tenant=tenant,
            vision=vision,
            name="تم ۱",
        )
        objective = ObjectiveModel.objects.create(
            tenant=tenant,
            theme=theme,
            title="هدف Q1",
            level="enterprise",
            status="active",
            owner_id=ppm_user.id,
        )

        # Create key result
        kr = KeyResultModel.objects.create(
            tenant=tenant,
            objective=objective,
            title="افزایش فروش",
            metric_type="number",
            start_value=0,
            target_value=100,
            current_value=0,
            owner_id=ppm_user.id,
        )
        assert kr.current_value == 0

        # Check-in
        check_in = KeyResultCheckInModel.objects.create(
            tenant=tenant,
            key_result=kr,
            value=45,
            notes="پیشرفت ۴۵٪",
            checked_by=ppm_user,
        )

        # Update KR
        kr.current_value = 45
        kr.save()
        kr.refresh_from_db()
        assert kr.current_value == 45


# ═══════════════════════════════════════════════════
# 4. Strategy Stage-Gate + Policy
# ═══════════════════════════════════════════════════


@pytest.mark.django_db
class TestStageGateAndPolicy:
    """تست فرآیند Stage-Gate و سیاست سازمانی."""

    def test_gate_review_lifecycle(self, tenant, ppm_user):
        """
        سناریو: ایجاد فرآیند Gate → تعریف Gate → بازبینی → تصمیم Go/No-Go.
        """
        from modules.strategy.backend.infrastructure.persistence.models import (
            GateProcessModel, GateModel, GateReviewModel,
            GateReviewerModel, GateDeliverableModel,
        )

        # 1. Create gate process
        process = GateProcessModel.objects.create(
            tenant=tenant,
            name="فرآیند Stage-Gate پروژه‌های نوآوری",
            is_active=True,
        )

        # 2. Create gates
        gate1 = GateModel.objects.create(
            tenant=tenant,
            process=process,
            name="Gate 1 — ایده‌سنجی",
            sequence=1,
        )
        gate2 = GateModel.objects.create(
            tenant=tenant,
            process=process,
            name="Gate 2 — امکان‌سنجی",
            sequence=2,
        )

        # 3. Create review for gate 1
        project_id = uuid.uuid4()
        review = GateReviewModel.objects.create(
            tenant=tenant,
            gate=gate1,
            entity_id=project_id,
            entity_type="project",
            status="pending",
        )

        # 4. Add reviewer
        reviewer = GateReviewerModel.objects.create(
            tenant=tenant,
            review=review,
            user=ppm_user,
            role="approver",
        )

        # 5. Add deliverable with document link
        doc_id = uuid.uuid4()
        deliverable = GateDeliverableModel.objects.create(
            tenant=tenant,
            review=review,
            name="طرح توجیهی",
            status="submitted",
            document_id=doc_id,
        )
        assert deliverable.document_id == doc_id

        # 6. Pass gate
        review.status = "passed"
        review.save()
        review.refresh_from_db()
        assert review.status == "passed"

    def test_policy_acknowledgment_flow(self, tenant, ppm_user):
        """
        سناریو: ایجاد سیاست → پیوند سند → تأیید توسط کاربر.
        """
        from modules.strategy.backend.infrastructure.persistence.models import (
            PolicyModel, PolicyAcknowledgmentModel,
        )

        doc_id = uuid.uuid4()
        policy = PolicyModel.objects.create(
            tenant=tenant,
            title="سیاست امنیت اطلاعات",
            status="active",
            document_id=doc_id,
            owner_id=ppm_user.id,
        )
        assert policy.document_id == doc_id

        # User acknowledges
        ack = PolicyAcknowledgmentModel.objects.create(
            tenant=tenant,
            policy=policy,
            user=ppm_user,
        )
        assert ack.user_id == ppm_user.id

        # Verify unique constraint
        acks = PolicyAcknowledgmentModel.objects.filter(
            tenant=tenant, policy=policy,
        )
        assert acks.count() == 1


# ═══════════════════════════════════════════════════
# 5. PPM Document Integration
# ═══════════════════════════════════════════════════


@pytest.mark.django_db
class TestPPMDocumentIntegration:
    """تست پیوند اسناد PPM با سرویس مدیریت اسناد."""

    def test_link_document_to_portfolio(self, tenant, ppm_user):
        """سناریو: پیوند سند به پرتفولیو."""
        from modules.ppm.backend.infrastructure.persistence.models import (
            PortfolioModel, PPMDocumentModel,
        )

        portfolio = PortfolioModel.objects.create(
            tenant=tenant,
            name="پرتفولیوی IT",
            code="PF-IT",
            status="active",
            owner_id=ppm_user.id,
        )

        doc_id = uuid.uuid4()
        doc_link = PPMDocumentModel.objects.create(
            tenant=tenant,
            entity_type="portfolio",
            entity_id=portfolio.id,
            document_id=doc_id,
            title="گزارش عملکرد سه‌ماهه",
            document_code="DOC-001",
            linked_by=ppm_user,
        )
        assert doc_link.entity_type == "portfolio"
        assert doc_link.document_id == doc_id

        # Verify query by entity
        docs = PPMDocumentModel.objects.filter(
            entity_type="portfolio",
            entity_id=portfolio.id,
        )
        assert docs.count() == 1

    def test_link_document_to_business_case(self, tenant, ppm_user):
        """سناریو: پیوند سند به کیس تجاری."""
        from modules.ppm.backend.infrastructure.persistence.models import (
            BusinessCaseModel, PPMDocumentModel,
        )

        bc = BusinessCaseModel.objects.create(
            tenant=tenant,
            project_id=uuid.uuid4(),
            title="کیس تجاری ERP",
            status="draft",
            prepared_by=ppm_user,
        )

        doc_link = PPMDocumentModel.objects.create(
            tenant=tenant,
            entity_type="business_case",
            entity_id=bc.id,
            document_id=uuid.uuid4(),
            title="مطالعه امکان‌سنجی",
            linked_by=ppm_user,
        )
        assert doc_link.entity_type == "business_case"

    def test_unique_constraint_prevents_duplicate_links(self, tenant, ppm_user):
        """تست محدودیت یکتایی — یک سند فقط یکبار به یک موجودیت پیوند شود."""
        from modules.ppm.backend.infrastructure.persistence.models import PPMDocumentModel
        from django.db import IntegrityError, transaction

        entity_id = uuid.uuid4()
        doc_id = uuid.uuid4()

        PPMDocumentModel.objects.create(
            tenant=tenant,
            entity_type="portfolio",
            entity_id=entity_id,
            document_id=doc_id,
            title="سند تست",
            linked_by=ppm_user,
        )

        with pytest.raises(IntegrityError):
            with transaction.atomic():
                PPMDocumentModel.objects.create(
                    tenant=tenant,
                    entity_type="portfolio",
                    entity_id=entity_id,
                    document_id=doc_id,
                    title="سند تکراری",
                    linked_by=ppm_user,
                )


# ═══════════════════════════════════════════════════
# 6. Event Bus — Cross-Module Event Delivery
# ═══════════════════════════════════════════════════


@pytest.mark.django_db
class TestCrossModuleEventDelivery:
    """تست تحویل رویداد بین ماژول‌ها."""

    def test_event_bus_subscribe_and_publish(self, fresh_event_bus):
        """تست ثبت و انتشار رویداد از طریق EventBus."""
        bus = fresh_event_bus
        received_events = []

        def handler(event):
            received_events.append(event)

        bus.subscribe(ProjectCompletedEvent, handler)

        event = ProjectCompletedEvent(
            tenant_id=str(uuid.uuid4()),
            aggregate_id=str(uuid.uuid4()),
            aggregate_type="project",
            metadata={"status": "completed"},
        )
        bus.publish(event)

        assert len(received_events) == 1
        assert "ProjectCompletedEvent" in received_events[0].event_type

    def test_multiple_handlers_for_same_event(self, fresh_event_bus):
        """تست چند هندلر برای یک رویداد."""
        bus = fresh_event_bus
        handler1_calls = []
        handler2_calls = []

        bus.subscribe(PortfolioCreatedEvent, lambda e: handler1_calls.append(e))
        bus.subscribe(PortfolioCreatedEvent, lambda e: handler2_calls.append(e))

        event = PortfolioCreatedEvent(
            tenant_id=str(uuid.uuid4()),
            aggregate_id=str(uuid.uuid4()),
            aggregate_type="portfolio",
        )
        bus.publish(event)

        assert len(handler1_calls) == 1
        assert len(handler2_calls) == 1

    def test_handler_isolation_on_error(self, fresh_event_bus):
        """تست ایزولاسیون هندلرها — خطای یکی نباید دیگری را مختل کند."""
        bus = fresh_event_bus
        successful = []

        def failing_handler(event):
            raise ValueError("Handler error")

        def success_handler(event):
            successful.append(event)

        bus.subscribe(ProjectCompletedEvent, failing_handler)
        bus.subscribe(ProjectCompletedEvent, success_handler)

        event = ProjectCompletedEvent(
            tenant_id=str(uuid.uuid4()),
            aggregate_id=str(uuid.uuid4()),
            aggregate_type="test",
        )

        # Publish should not raise — errors are caught per-handler
        bus.publish(event)

        # The success_handler should have been called despite failing_handler
        assert len(successful) == 1


# ═══════════════════════════════════════════════════
# 7. Multi-Tenant Data Isolation
# ═══════════════════════════════════════════════════


@pytest.mark.django_db
class TestMultiTenantIsolation:
    """تست ایزولاسیون داده بین تنانت‌ها."""

    def test_ppm_portfolio_tenant_isolation(self, tenant):
        """دو تنانت نباید داده‌های یکدیگر را ببینند."""
        from apps.core.tenant.models import Tenant, Domain
        from modules.ppm.backend.infrastructure.persistence.models import (
            PortfolioModel,
        )

        # Create second tenant with unique schema
        tenant2, _ = Tenant.objects.get_or_create(
            schema_name="tenant2_ppm",
            defaults={
                "name": "تنانت ۲",
                "slug": "tenant-2-ppm",
            },
        )
        Domain.objects.get_or_create(
            domain="tenant2ppm.localhost",
            tenant=tenant2,
            defaults={"is_primary": True},
        )

        # Create portfolios in each tenant (idempotent)
        p1, _ = PortfolioModel.objects.get_or_create(
            tenant=tenant,
            code="PF-T1-ISO",
            defaults={"name": "پرتفولیو تنانت ۱", "status": "active"},
        )
        p2, _ = PortfolioModel.objects.get_or_create(
            tenant=tenant2,
            code="PF-T2-ISO",
            defaults={"name": "پرتفولیو تنانت ۲", "status": "active"},
        )

        # Verify isolation
        t1_portfolios = PortfolioModel.objects.filter(tenant=tenant, code="PF-T1-ISO")
        t2_portfolios = PortfolioModel.objects.filter(tenant=tenant2, code="PF-T2-ISO")

        assert t1_portfolios.count() == 1
        assert t2_portfolios.count() == 1
        assert t1_portfolios.first().tenant_id == tenant.id
        assert t2_portfolios.first().tenant_id == tenant2.id

    def test_strategy_objective_tenant_isolation(self, tenant):
        """اهداف استراتژیک بین تنانت‌ها ایزوله باشند."""
        from apps.core.tenant.models import Tenant, Domain
        from modules.strategy.backend.infrastructure.persistence.models import (
            VisionModel, StrategicThemeModel, ObjectiveModel,
        )

        tenant2, _ = Tenant.objects.get_or_create(
            schema_name="tenant3_str",
            defaults={
                "name": "تنانت ۳",
                "slug": "tenant-3-str",
            },
        )
        Domain.objects.get_or_create(
            domain="tenant3str.localhost",
            tenant=tenant2,
            defaults={"is_primary": True},
        )

        # Create visions for each tenant
        v1 = VisionModel.objects.create(tenant=tenant, title="چشم‌انداز ۱", description="بیانیه ۱", status="active")
        v2 = VisionModel.objects.create(tenant=tenant2, title="چشم‌انداز ۲", description="بیانیه ۲", status="active")

        t1 = StrategicThemeModel.objects.create(tenant=tenant, vision=v1, name="تم ۱")
        t2 = StrategicThemeModel.objects.create(tenant=tenant2, vision=v2, name="تم ۲")

        ObjectiveModel.objects.create(tenant=tenant, theme=t1, title="هدف تنانت ۱", level="enterprise", status="active")
        ObjectiveModel.objects.create(tenant=tenant2, theme=t2, title="هدف تنانت ۲", level="enterprise", status="active")

        assert ObjectiveModel.objects.filter(tenant=tenant).count() == 1
        assert ObjectiveModel.objects.filter(tenant=tenant2).count() == 1


# ═══════════════════════════════════════════════════
# 8. Analytics Dependency Graph
# ═══════════════════════════════════════════════════


@pytest.mark.django_db
class TestEnterpriseDependencyGraph:
    """تست API گراف وابستگی سازمانی."""

    def test_dependency_graph_api_returns_structure(self, authenticated_client):
        """بررسی ساختار خروجی API گراف وابستگی."""
        client, user = authenticated_client

        response = client.get("/api/v1/analytics/dependency-graph/")
        assert response.status_code == 200

        data = response.json()["data"]
        assert "nodes" in data
        assert "edges" in data
        assert "summary" in data
        assert "total_nodes" in data["summary"]
        assert "total_edges" in data["summary"]

    def test_dependency_graph_scope_filter(self, authenticated_client):
        """بررسی فیلتر scope در API."""
        client, user = authenticated_client

        # Filter by PPM scope
        response = client.get("/api/v1/analytics/dependency-graph/?scope=ppm")
        assert response.status_code == 200

        # Filter by strategy scope
        response = client.get("/api/v1/analytics/dependency-graph/?scope=strategy")
        assert response.status_code == 200
