"""基于真实成本与财务确认价值计算商业定价可行区间。"""

from __future__ import annotations

from decimal import ROUND_HALF_UP, Decimal

from sqlalchemy.orm import Session

from app.schemas.commercial import (
    CommercialPricingCurrencyScenarioRead,
    CommercialPricingScenarioRead,
    CommercialPricingScenarioWrite,
)
from app.services.commercial_analytics import CommercialAnalyticsService

FOUR_PLACES = Decimal("0.0001")
SIX_PLACES = Decimal("0.000001")


class CommercialPricingService:
    """输出价格走廊，不写合同，也不把风险暴露或预计节省当价值。"""

    def __init__(self, db: Session) -> None:
        self.db = db

    def build(
        self,
        tenant_id: str,
        payload: CommercialPricingScenarioWrite,
    ) -> CommercialPricingScenarioRead:
        analytics = CommercialAnalyticsService(self.db).build(
            tenant_id,
            start=payload.start,
            end=payload.end,
            as_of=payload.as_of,
        )
        costs = {item.currency: Decimal(item.amount) for item in analytics.internal_costs.values}
        savings = {
            item.currency: Decimal(item.amount)
            for item in analytics.verified_cash_savings.values
        }
        currencies = sorted(set(costs) | set(savings))
        scenarios = [
            self._currency_scenario(
                currency,
                cost=costs.get(currency),
                saving=savings.get(currency),
                target_margin=payload.target_contribution_margin_rate,
                max_value_share=payload.max_verified_savings_share,
            )
            for currency in currencies
        ]
        recommended_model = self._recommended_model(scenarios)
        if not scenarios:
            evidence_status = "unavailable"
        elif all(item.status == "feasible" for item in scenarios):
            evidence_status = "complete"
        else:
            evidence_status = "partial"
        notes = [
            "价格下限仅使用内部成本事实；价值上限仅使用独立财务确认、canonical、已计冲回的现金节省。",
            "风险暴露、预计机会、未确认结果和工时估值不进入本定价走廊。",
            "输出是合同谈判边界，不会自动创建、激活或修改客户套餐。",
        ]
        notes.extend(analytics.data_quality_issues)
        return CommercialPricingScenarioRead(
            tenant_id=tenant_id,
            start=analytics.start,
            end=analytics.end,
            as_of=analytics.as_of,
            target_contribution_margin_rate=payload.target_contribution_margin_rate,
            max_verified_savings_share=payload.max_verified_savings_share,
            recommended_model=recommended_model,
            scenarios=scenarios,
            evidence_status=evidence_status,
            notes=list(dict.fromkeys(notes)),
        )

    @staticmethod
    def _currency_scenario(
        currency: str,
        *,
        cost: Decimal | None,
        saving: Decimal | None,
        target_margin: Decimal,
        max_value_share: Decimal,
    ) -> CommercialPricingCurrencyScenarioRead:
        normalized_cost = max(Decimal("0"), cost) if cost is not None else None
        normalized_saving = max(Decimal("0"), saving) if saving is not None else None
        floor = (
            _money(normalized_cost / (Decimal("1") - target_margin))
            if normalized_cost is not None
            else None
        )
        ceiling = (
            _money(normalized_saving * max_value_share)
            if normalized_saving is not None
            else None
        )
        if floor is None and ceiling is None:
            return CommercialPricingCurrencyScenarioRead(
                currency=currency,
                status="unavailable",
                reason="缺少同币种内部成本与财务确认现金节省。",
            )
        if floor is not None and ceiling is None:
            return CommercialPricingCurrencyScenarioRead(
                currency=currency,
                status="cost_only",
                internal_cost=_money(normalized_cost),
                minimum_sustainable_charge=floor,
                reason="已有成本下限，但缺少财务确认价值，暂不能计算价值定价上限。",
            )
        if floor is None:
            return CommercialPricingCurrencyScenarioRead(
                currency=currency,
                status="unavailable",
                verified_cash_savings=_money(normalized_saving),
                maximum_value_aligned_charge=ceiling,
                reason="已有客户价值，但缺少平台内部成本，不能给出可持续报价。",
            )
        customer_roi = (
            _ratio((normalized_saving - floor) / floor)
            if normalized_saving is not None and floor > 0
            else None
        )
        margin_at_ceiling = (
            _ratio((ceiling - normalized_cost) / ceiling)
            if ceiling is not None and ceiling > 0 and normalized_cost is not None
            else None
        )
        feasible = ceiling is not None and floor <= ceiling
        return CommercialPricingCurrencyScenarioRead(
            currency=currency,
            status="feasible" if feasible else "insufficient_value",
            internal_cost=_money(normalized_cost),
            verified_cash_savings=_money(normalized_saving),
            minimum_sustainable_charge=floor,
            maximum_value_aligned_charge=ceiling,
            maximum_success_fee=_money(max(Decimal("0"), ceiling - floor)),
            customer_roi_at_minimum_charge=customer_roi,
            contribution_margin_at_value_ceiling=margin_at_ceiling,
            reason=(
                "成本可持续下限低于客户价值上限，可采用基础订阅费加封顶成功费。"
                if feasible
                else "成本下限高于客户价值上限，应先降低交付成本或提高可验证价值，不能强行报价。"
            ),
        )

    @staticmethod
    def _recommended_model(
        scenarios: list[CommercialPricingCurrencyScenarioRead],
    ) -> str:
        if any(item.status == "insufficient_value" for item in scenarios):
            return "optimize_unit_economics"
        if any(item.status == "feasible" for item in scenarios):
            return "hybrid"
        if any(item.status == "cost_only" for item in scenarios):
            return "subscription"
        return "pilot_collecting"


def _money(value: Decimal | None) -> Decimal | None:
    return value.quantize(FOUR_PLACES, rounding=ROUND_HALF_UP) if value is not None else None


def _ratio(value: Decimal) -> Decimal:
    return value.quantize(SIX_PLACES, rounding=ROUND_HALF_UP)
