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

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

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

from src.core.constants import OpportunityStage, ProposalStatus
from src.core.exceptions import NotFoundError
from src.apps.sales.models.proposal import Proposal, ProposalLineItem
from src.apps.sales.models.opportunity import Opportunity


class ProposalService:

    @staticmethod
    async def create(
        db: AsyncSession,
        tenant_id: str,
        data: dict,
        current_user,
    ) -> Proposal:
        line_items_data = data.pop("line_items", [])
        opportunity_id = data.get("opportunity_id")

        # Generate quote number: P-{YEAR}-{4-digit-seq}
        year = datetime.now(timezone.utc).year
        count = (
            await db.execute(
                select(func.count()).select_from(Proposal).where(
                    Proposal.tenant_id == tenant_id
                )
            )
        ).scalar_one()
        quote_number = f"P-{year}-{(count + 1):04d}"

        # Compute totals
        subtotal = sum(
            (Decimal(str(item["quantity"])) * Decimal(str(item["unit_price"])))
            for item in line_items_data
        ) if line_items_data else Decimal("0")
        tax_rate = Decimal("0.13")
        tax_amount = subtotal * tax_rate
        total_amount = subtotal + tax_amount

        proposal = Proposal(
            tenant_id=tenant_id,
            quote_number=quote_number,
            status=ProposalStatus.DRAFT.value,
            subtotal=subtotal,
            tax_rate=tax_rate,
            tax_amount=tax_amount,
            total_amount=total_amount,
            **data,
        )
        db.add(proposal)
        await db.flush()

        for idx, item in enumerate(line_items_data):
            qty = Decimal(str(item["quantity"]))
            unit_price = Decimal(str(item["unit_price"]))
            line_total = qty * unit_price
            li = ProposalLineItem(
                proposal_id=proposal.id,
                tenant_id=tenant_id,
                sort_order=idx,
                description=item["description"],
                quantity=qty,
                unit_price=unit_price,
                line_total=line_total,
            )
            db.add(li)

        await db.flush()
        result = await db.execute(
            select(Proposal)
            .options(selectinload(Proposal.line_items))
            .where(Proposal.id == proposal.id)
        )
        return result.scalar_one()

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

    @staticmethod
    async def send(
        db: AsyncSession,
        tenant_id: str,
        proposal_id: UUID,
        current_user,
    ) -> Proposal:
        proposal = await ProposalService.get_by_id(db, tenant_id, proposal_id)

        proposal.status = ProposalStatus.SENT.value
        proposal.sent_at = datetime.now(timezone.utc)

        # Update linked opportunity stage if present
        if proposal.opportunity_id:
            result = await db.execute(
                select(Opportunity).where(
                    Opportunity.id == proposal.opportunity_id,
                    Opportunity.tenant_id == tenant_id,
                    Opportunity.deleted_at.is_(None),
                )
            )
            opp = result.scalar_one_or_none()
            if opp:
                opp.stage = OpportunityStage.PROPOSAL_SENT.value
                opp.stage_entered_at = datetime.now(timezone.utc)

        await db.flush()
        result = await db.execute(
            select(Proposal)
            .options(selectinload(Proposal.line_items))
            .where(Proposal.id == proposal.id)
        )
        return result.scalar_one()

    @staticmethod
    async def mark_accepted(
        db: AsyncSession,
        tenant_id: str,
        proposal_id: UUID,
        current_user,
    ) -> Proposal:
        proposal = await ProposalService.get_by_id(db, tenant_id, proposal_id)

        proposal.status = ProposalStatus.ACCEPTED.value
        proposal.accepted_at = datetime.now(timezone.utc)

        # Update linked opportunity stage if present
        if proposal.opportunity_id:
            result = await db.execute(
                select(Opportunity).where(
                    Opportunity.id == proposal.opportunity_id,
                    Opportunity.tenant_id == tenant_id,
                    Opportunity.deleted_at.is_(None),
                )
            )
            opp = result.scalar_one_or_none()
            if opp:
                opp.stage = OpportunityStage.CONTRACT_SIGNED.value
                opp.stage_entered_at = datetime.now(timezone.utc)

        await db.flush()
        result = await db.execute(
            select(Proposal)
            .options(selectinload(Proposal.line_items))
            .where(Proposal.id == proposal.id)
        )
        return result.scalar_one()
