"""Phase 6.2 playbook, forecast, goal, target, product, contact-role, competitor services."""
from __future__ import annotations

from datetime import datetime, timezone
from decimal import Decimal
from uuid import UUID

from sqlalchemy.ext.asyncio import AsyncSession

from app.events.publisher import InMemoryEventPublisher, get_event_publisher
from app.events.types import CrmEventType
from app.models.sales_engine import (
    OpportunityCompetitor,
    OpportunityContact,
    OpportunityProduct,
    SalesForecast,
    SalesGoal,
    SalesPlaybook,
    SalesPlaybookAssignment,
    SalesPlaybookStep,
    SalesTarget,
    WinLossCategory,
    WinLossReason,
)
from app.models.types import (
    ForecastKind,
    PlaybookAssignmentStatus,
)
from app.repositories.sales_engine import (
    OpportunityCompetitorRepository,
    OpportunityContactRepository,
    OpportunityProductRepository,
    OpportunityRepository,
    SalesForecastRepository,
    SalesGoalRepository,
    SalesPlaybookAssignmentRepository,
    SalesPlaybookRepository,
    SalesPlaybookStepRepository,
    SalesTargetRepository,
    WinLossCategoryRepository,
    WinLossReasonRepository,
)
from app.schemas.sales_engine import (
    ForecastComputeRequest,
    GoalCreate,
    OpportunityCompetitorCreate,
    OpportunityContactCreate,
    OpportunityProductCreate,
    PlaybookAssignRequest,
    PlaybookCompleteStepRequest,
    PlaybookCreate,
    PlaybookStepCreate,
    TargetCreate,
    WinLossCategoryCreate,
    WinLossReasonCreate,
)
from app.services.opportunity_service import SalesAuditService
from app.validators import validate_non_empty
from app.validators.sales_engine import validate_non_negative, validate_probability
from shared.exceptions import AppError, NotFoundError
from shared.security import CurrentUser


class PlaybookService:
    def __init__(
        self,
        session: AsyncSession,
        publisher: InMemoryEventPublisher | None = None,
    ) -> None:
        self.session = session
        self.repo = SalesPlaybookRepository(session)
        self.steps = SalesPlaybookStepRepository(session)
        self.assignments = SalesPlaybookAssignmentRepository(session)
        self.audit = SalesAuditService(session)
        self.publisher = publisher or get_event_publisher()

    async def create(self, tenant_id: UUID, body: PlaybookCreate) -> SalesPlaybook:
        code = validate_non_empty(body.code, field="code")
        if await self.repo.get_by_code_version(tenant_id, code, body.version):
            raise AppError(
                "نسخه پلی‌بوک تکراری است",
                status_code=409,
                error_code="playbook_version_exists",
            )
        entity = SalesPlaybook(
            tenant_id=tenant_id,
            name=validate_non_empty(body.name, field="name"),
            code=code,
            description=body.description,
            version=body.version,
            pipeline_id=body.pipeline_id,
            is_active=body.is_active,
        )
        await self.repo.add(entity)
        await self.session.commit()
        await self.session.refresh(entity)
        return entity

    async def add_step(
        self, tenant_id: UUID, body: PlaybookStepCreate
    ) -> SalesPlaybookStep:
        playbook = await self.repo.get(tenant_id, body.playbook_id)
        if playbook is None:
            raise NotFoundError("پلی‌بوک یافت نشد", error_code="playbook_not_found")
        entity = SalesPlaybookStep(
            tenant_id=tenant_id,
            playbook_id=body.playbook_id,
            title=validate_non_empty(body.title, field="title"),
            step_kind=body.step_kind,
            position=body.position,
            is_required=body.is_required,
            checklist_items=body.checklist_items,
            template_body=body.template_body,
        )
        await self.steps.add(entity)
        await self.session.commit()
        await self.session.refresh(entity)
        return entity

    async def assign(
        self,
        tenant_id: UUID,
        body: PlaybookAssignRequest,
        *,
        actor: CurrentUser | None = None,
    ) -> SalesPlaybookAssignment:
        playbook = await self.repo.get(tenant_id, body.playbook_id)
        if playbook is None:
            raise NotFoundError("پلی‌بوک یافت نشد", error_code="playbook_not_found")
        if body.opportunity_id is None and body.pipeline_id is None:
            raise AppError(
                "opportunity_id یا pipeline_id الزامی است",
                status_code=422,
                error_code="playbook_target_required",
            )
        actor_id = actor.user_id if actor else None
        entity = SalesPlaybookAssignment(
            tenant_id=tenant_id,
            playbook_id=body.playbook_id,
            opportunity_id=body.opportunity_id,
            pipeline_id=body.pipeline_id or playbook.pipeline_id,
            status=PlaybookAssignmentStatus.ASSIGNED,
            assigned_by=actor_id,
            completed_steps=[],
            completion_percent=0,
        )
        await self.assignments.add(entity)
        await self.audit.record(
            tenant_id=tenant_id,
            entity_type="playbook_assignment",
            entity_id=entity.id,
            action="assign",
            actor_user_id=actor_id,
        )
        await self.session.commit()
        await self.session.refresh(entity)
        self.publisher.publish(
            event_type=CrmEventType.PLAYBOOK_ASSIGNED,
            aggregate_type="playbook_assignment",
            aggregate_id=entity.id,
            tenant_id=tenant_id,
            payload={"playbook_id": str(entity.playbook_id)},
        )
        return entity

    async def complete_step(
        self,
        tenant_id: UUID,
        assignment_id: UUID,
        body: PlaybookCompleteStepRequest,
        *,
        actor: CurrentUser | None = None,
    ) -> SalesPlaybookAssignment:
        entity = await self.assignments.get(tenant_id, assignment_id)
        if entity is None:
            raise NotFoundError(
                "تخصیص پلی‌بوک یافت نشد", error_code="playbook_assignment_not_found"
            )
        steps = await self.steps.list_by_playbook(tenant_id, entity.playbook_id)
        step_ids = {str(s.id) for s in steps}
        if str(body.step_id) not in step_ids:
            raise AppError(
                "مرحله متعلق به این پلی‌بوک نیست",
                status_code=422,
                error_code="invalid_playbook_step",
            )
        completed = list(entity.completed_steps or [])
        sid = str(body.step_id)
        if sid not in completed:
            completed.append(sid)
        entity.completed_steps = completed
        total = max(len(steps), 1)
        entity.completion_percent = int(round(100 * len(completed) / total))
        entity.status = PlaybookAssignmentStatus.IN_PROGRESS
        if entity.completion_percent >= 100:
            entity.status = PlaybookAssignmentStatus.COMPLETED
            entity.completed_at = datetime.now(timezone.utc)
            self.publisher.publish(
                event_type=CrmEventType.PLAYBOOK_COMPLETED,
                aggregate_type="playbook_assignment",
                aggregate_id=entity.id,
                tenant_id=tenant_id,
                payload={"playbook_id": str(entity.playbook_id)},
            )
        await self.session.commit()
        await self.session.refresh(entity)
        return entity

    async def list(self, tenant_id: UUID, *, offset: int, limit: int):
        return await self.repo.list_by_tenant(tenant_id, offset=offset, limit=limit)

    async def list_steps(self, tenant_id: UUID, playbook_id: UUID):
        return await self.steps.list_by_playbook(tenant_id, playbook_id)


class ForecastService:
    def __init__(
        self,
        session: AsyncSession,
        publisher: InMemoryEventPublisher | None = None,
    ) -> None:
        self.session = session
        self.repo = SalesForecastRepository(session)
        self.opportunities = OpportunityRepository(session)
        self.publisher = publisher or get_event_publisher()

    async def compute(
        self, tenant_id: UUID, body: ForecastComputeRequest
    ) -> SalesForecast:
        rows = await self.opportunities.list_open_in_period(
            tenant_id,
            period_start=body.period_start,
            period_end=body.period_end,
            pipeline_id=body.pipeline_id,
        )
        pipeline_amount = Decimal("0")
        weighted_amount = Decimal("0")
        committed_amount = Decimal("0")
        for opp in rows:
            revenue = opp.expected_revenue or opp.amount or Decimal("0")
            pipeline_amount += revenue
            weighted_amount += revenue * Decimal(opp.probability) / Decimal(100)
            if opp.probability >= 80:
                committed_amount += revenue
        entity = SalesForecast(
            tenant_id=tenant_id,
            name=validate_non_empty(body.name, field="name"),
            kind=body.kind,
            period=body.period,
            period_start=body.period_start,
            period_end=body.period_end,
            pipeline_id=body.pipeline_id,
            owner_user_id=body.owner_user_id,
            pipeline_amount=pipeline_amount,
            weighted_amount=weighted_amount,
            committed_amount=committed_amount,
            currency_code=body.currency_code,
            opportunity_count=len(rows),
        )
        await self.repo.add(entity)
        await self.session.commit()
        await self.session.refresh(entity)
        self.publisher.publish(
            event_type=CrmEventType.FORECAST_UPDATED,
            aggregate_type="sales_forecast",
            aggregate_id=entity.id,
            tenant_id=tenant_id,
            payload={
                "kind": entity.kind.value,
                "weighted_amount": str(entity.weighted_amount),
                "pipeline_amount": str(entity.pipeline_amount),
            },
        )
        return entity

    async def list(self, tenant_id: UUID, *, offset: int, limit: int):
        return await self.repo.list_by_tenant(tenant_id, offset=offset, limit=limit)


class GoalService:
    def __init__(self, session: AsyncSession) -> None:
        self.session = session
        self.repo = SalesGoalRepository(session)

    async def create(self, tenant_id: UUID, body: GoalCreate) -> SalesGoal:
        entity = SalesGoal(
            tenant_id=tenant_id,
            name=validate_non_empty(body.name, field="name"),
            scope=body.scope,
            metric=body.metric,
            owner_user_id=body.owner_user_id,
            team_key=body.team_key,
            department_key=body.department_key,
            target_value=validate_non_negative(body.target_value, field="target_value"),
            period_start=body.period_start,
            period_end=body.period_end,
            is_active=body.is_active,
        )
        await self.repo.add(entity)
        await self.session.commit()
        await self.session.refresh(entity)
        return entity

    async def list(self, tenant_id: UUID, *, offset: int, limit: int):
        return await self.repo.list_by_tenant(tenant_id, offset=offset, limit=limit)


class TargetService:
    def __init__(self, session: AsyncSession) -> None:
        self.session = session
        self.repo = SalesTargetRepository(session)

    async def create(self, tenant_id: UUID, body: TargetCreate) -> SalesTarget:
        target = validate_non_negative(body.target_value, field="target_value")
        actual = validate_non_negative(body.actual_value, field="actual_value")
        percent = (
            (actual / target * Decimal(100)) if target > 0 else Decimal("0")
        )
        entity = SalesTarget(
            tenant_id=tenant_id,
            name=validate_non_empty(body.name, field="name"),
            period=body.period,
            period_start=body.period_start,
            period_end=body.period_end,
            goal_id=body.goal_id,
            owner_user_id=body.owner_user_id,
            target_value=target,
            actual_value=actual,
            achievement_percent=percent.quantize(Decimal("0.0001")),
        )
        await self.repo.add(entity)
        await self.session.commit()
        await self.session.refresh(entity)
        return entity

    async def list(self, tenant_id: UUID, *, offset: int, limit: int):
        return await self.repo.list_by_tenant(tenant_id, offset=offset, limit=limit)


class SalesProcessService:
    def __init__(self, session: AsyncSession) -> None:
        self.session = session
        from app.repositories.sales_engine import SalesProcessRepository
        from app.models.sales_engine import SalesProcess

        self.repo = SalesProcessRepository(session)
        self._model = SalesProcess

    async def create(
        self,
        tenant_id: UUID,
        *,
        name: str,
        code: str,
        description: str | None = None,
        pipeline_id: UUID | None = None,
    ):
        code_v = validate_non_empty(code, field="code")
        entity = self._model(
            tenant_id=tenant_id,
            name=validate_non_empty(name, field="name"),
            code=code_v,
            description=description,
            pipeline_id=pipeline_id,
            is_active=True,
        )
        await self.repo.add(entity)
        await self.session.commit()
        await self.session.refresh(entity)
        return entity

    async def list(self, tenant_id: UUID, *, offset: int, limit: int):
        return await self.repo.list_by_tenant(tenant_id, offset=offset, limit=limit)


class OpportunityExtrasService:
    def __init__(self, session: AsyncSession) -> None:
        self.session = session
        self.products = OpportunityProductRepository(session)
        self.contacts = OpportunityContactRepository(session)
        self.competitors = OpportunityCompetitorRepository(session)
        self.opportunities = OpportunityRepository(session)

    async def _require_open_opportunity(
        self, tenant_id: UUID, opportunity_id: UUID
    ):
        from app.validators.sales_engine import ensure_opportunity_mutable

        opp = await self.opportunities.get(tenant_id, opportunity_id)
        if opp is None:
            raise NotFoundError("فرصت یافت نشد", error_code="opportunity_not_found")
        ensure_opportunity_mutable(opp)
        return opp

    async def add_product(
        self, tenant_id: UUID, body: OpportunityProductCreate
    ) -> OpportunityProduct:
        await self._require_open_opportunity(tenant_id, body.opportunity_id)
        qty = validate_non_negative(body.quantity, field="quantity")
        price = validate_non_negative(body.unit_price, field="unit_price")
        discount = validate_non_negative(body.discount_percent, field="discount_percent")
        if discount > 100:
            raise AppError(
                "تخفیف نمی‌تواند بیش از ۱۰۰ باشد",
                status_code=422,
                error_code="invalid_discount",
            )
        revenue = qty * price * (Decimal(100) - discount) / Decimal(100)
        entity = OpportunityProduct(
            tenant_id=tenant_id,
            opportunity_id=body.opportunity_id,
            product_ref_id=validate_non_empty(body.product_ref_id, field="product_ref_id"),
            product_name=body.product_name,
            quantity=qty,
            unit_price=price,
            discount_percent=discount,
            expected_revenue=revenue,
        )
        await self.products.add(entity)
        await self.session.commit()
        await self.session.refresh(entity)
        return entity

    async def add_contact(
        self, tenant_id: UUID, body: OpportunityContactCreate
    ) -> OpportunityContact:
        await self._require_open_opportunity(tenant_id, body.opportunity_id)
        entity = OpportunityContact(
            tenant_id=tenant_id,
            opportunity_id=body.opportunity_id,
            contact_id=body.contact_id,
            role=body.role,
            custom_role_label=body.custom_role_label,
            is_primary=body.is_primary,
        )
        await self.contacts.add(entity)
        await self.session.commit()
        await self.session.refresh(entity)
        return entity

    async def add_competitor(
        self, tenant_id: UUID, body: OpportunityCompetitorCreate
    ) -> OpportunityCompetitor:
        await self._require_open_opportunity(tenant_id, body.opportunity_id)
        entity = OpportunityCompetitor(
            tenant_id=tenant_id,
            opportunity_id=body.opportunity_id,
            competitor_name=validate_non_empty(
                body.competitor_name, field="competitor_name"
            ),
            strength=body.strength,
            weakness=body.weakness,
            notes=body.notes,
            win_probability=validate_probability(body.win_probability),
        )
        await self.competitors.add(entity)
        await self.session.commit()
        await self.session.refresh(entity)
        return entity


class WinLossService:
    def __init__(self, session: AsyncSession) -> None:
        self.session = session
        self.categories = WinLossCategoryRepository(session)
        self.reasons = WinLossReasonRepository(session)

    async def create_category(
        self, tenant_id: UUID, body: WinLossCategoryCreate
    ) -> WinLossCategory:
        code = validate_non_empty(body.code, field="code")
        if await self.categories.get_by_code(tenant_id, code):
            raise AppError("کد دسته تکراری است", status_code=409, error_code="code_exists")
        entity = WinLossCategory(
            tenant_id=tenant_id,
            code=code,
            name=validate_non_empty(body.name, field="name"),
            parent_category_id=body.parent_category_id,
            is_active=body.is_active,
        )
        await self.categories.add(entity)
        await self.session.commit()
        await self.session.refresh(entity)
        return entity

    async def create_reason(
        self, tenant_id: UUID, body: WinLossReasonCreate
    ) -> WinLossReason:
        code = validate_non_empty(body.code, field="code")
        if await self.reasons.get_by_code(tenant_id, code):
            raise AppError("کد دلیل تکراری است", status_code=409, error_code="code_exists")
        entity = WinLossReason(
            tenant_id=tenant_id,
            code=code,
            name=validate_non_empty(body.name, field="name"),
            outcome=body.outcome,
            category_id=body.category_id,
            is_active=body.is_active,
        )
        await self.reasons.add(entity)
        await self.session.commit()
        await self.session.refresh(entity)
        return entity

    async def list_categories(self, tenant_id: UUID, *, offset: int, limit: int):
        return await self.categories.list_by_tenant(
            tenant_id, offset=offset, limit=limit
        )

    async def list_reasons(self, tenant_id: UUID, *, offset: int, limit: int):
        return await self.reasons.list_by_tenant(tenant_id, offset=offset, limit=limit)
