# FILE: src/apps/sales/services/opportunity_service.py
from __future__ import annotations

from datetime import datetime, timezone
from typing import Optional
from uuid import UUID

from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload

from src.core.constants import OpportunityStage
from src.core.exceptions import NotFoundError
from src.apps.sales.models.opportunity import Opportunity
from src.apps.sales.models.activity import Activity


class OpportunityService:

    @staticmethod
    async def get_kanban(
        db: AsyncSession,
        tenant_id: str,
        search: Optional[str] = None,
        assigned_to: Optional[UUID] = None,
    ) -> dict:
        filters = [
            Opportunity.tenant_id == tenant_id,
            Opportunity.deleted_at.is_(None),
        ]
        if search:
            filters.append(Opportunity.family_name.ilike(f"%{search}%"))
        if assigned_to:
            filters.append(Opportunity.assigned_to == assigned_to)

        result = await db.execute(
            select(Opportunity)
            .where(*filters)
            .order_by(Opportunity.stage_entered_at.asc())
        )
        opportunities = result.scalars().all()

        now = datetime.now(timezone.utc)
        stages = [s.value for s in OpportunityStage]
        kanban: dict = {stage: [] for stage in stages}

        for opp in opportunities:
            stage_key = opp.stage if opp.stage in kanban else OpportunityStage.INQUIRY.value
            if opp.stage_entered_at:
                entered = opp.stage_entered_at
                if entered.tzinfo is None:
                    entered = entered.replace(tzinfo=timezone.utc)
                days_in_stage = (now - entered).days
            else:
                days_in_stage = 0

            kanban[stage_key].append({
                "id": opp.id,
                "family_name": opp.family_name,
                "care_type": opp.care_type,
                "section_id": opp.section_id,
                "plot_id": opp.plot_id,
                "estimated_value": opp.estimated_value,
                "stage": opp.stage,
                "stage_entered_at": opp.stage_entered_at,
                "assigned_to": opp.assigned_to,
                "days_in_stage": days_in_stage,
                "next_action": opp.next_action,
                "notes": opp.notes,
                "created_at": opp.created_at,
                "updated_at": opp.updated_at,
            })

        return kanban

    @staticmethod
    async def get_by_id(
        db: AsyncSession,
        tenant_id: str,
        opp_id: UUID,
    ) -> Opportunity:
        result = await db.execute(
            select(Opportunity)
            .options(selectinload(Opportunity.activities))
            .where(
                Opportunity.id == opp_id,
                Opportunity.tenant_id == tenant_id,
                Opportunity.deleted_at.is_(None),
            )
        )
        opp = result.scalar_one_or_none()
        if not opp:
            raise NotFoundError("Opportunity not found")
        return opp

    @staticmethod
    async def create(
        db: AsyncSession,
        tenant_id: str,
        data: dict,
        current_user,
    ) -> Opportunity:
        opp = Opportunity(
            tenant_id=tenant_id,
            stage=OpportunityStage.INQUIRY.value,
            stage_entered_at=datetime.now(timezone.utc),
            **data,
        )
        db.add(opp)
        await db.flush()

        activity = Activity(
            tenant_id=tenant_id,
            opportunity_id=opp.id,
            user_id=current_user.id,
            activity_type="created",
            subject="Opportunity created",
        )
        db.add(activity)
        await db.flush()
        await db.refresh(opp)
        return opp

    @staticmethod
    async def update(
        db: AsyncSession,
        tenant_id: str,
        opp_id: UUID,
        data: dict,
        current_user,
    ) -> Opportunity:
        opp = await OpportunityService.get_by_id(db, tenant_id, opp_id)

        old_stage = opp.stage
        new_stage = data.get("stage")
        stage_changed = new_stage is not None and new_stage != old_stage

        for field, value in data.items():
            setattr(opp, field, value)

        if stage_changed:
            opp.stage_entered_at = datetime.now(timezone.utc)

        await db.flush()

        activity_type = "stage_changed" if stage_changed else "updated"
        subject = f"Stage changed to {new_stage}" if stage_changed else "Opportunity updated"
        activity = Activity(
            tenant_id=tenant_id,
            opportunity_id=opp.id,
            user_id=current_user.id,
            activity_type=activity_type,
            subject=subject,
        )
        db.add(activity)
        await db.flush()
        await db.refresh(opp)
        return opp

    @staticmethod
    async def soft_delete(
        db: AsyncSession,
        tenant_id: str,
        opp_id: UUID,
        lost_reason: Optional[str],
        current_user,
    ) -> Opportunity:
        opp = await OpportunityService.get_by_id(db, tenant_id, opp_id)

        opp.soft_delete()
        opp.stage = OpportunityStage.LOST.value
        opp.lost_reason = lost_reason

        activity = Activity(
            tenant_id=tenant_id,
            opportunity_id=opp.id,
            user_id=current_user.id,
            activity_type="marked_lost",
            subject="Opportunity marked as lost",
            notes=lost_reason,
        )
        db.add(activity)
        await db.flush()
        await db.refresh(opp)
        return opp
