"""Structured quote draft planning for Standard Quotes."""

from __future__ import annotations

from datetime import datetime, timezone
from typing import Any, Literal

from pydantic import BaseModel, ConfigDict, Field, ValidationError, computed_field

from .context import DealAssumption, DealContextV1
from .profiles import QuoteEnvironmentProfileV1
from .readiness import QuoteReadinessResult


class _QuotePlanModel(BaseModel):
    model_config = ConfigDict(extra="ignore")

    def to_dict(self) -> dict[str, Any]:
        return self.model_dump(exclude_none=False, by_alias=True)


class QuoteLineDraft(_QuotePlanModel):
    product_id: str | None = None
    product_code: str | None = None
    product_name: str | None = None
    quantity: float = Field(gt=0.0)
    unit_price: float | None = None
    discount_pct: float | None = None
    term_months: int | None = None


class QuoteDraftPlanV1(_QuotePlanModel):
    version: int = 1
    quote_request_id: str
    crm_org_id: str
    opportunity_id: str
    engine: Literal["standard_quotes"]
    confidence: float = Field(ge=0.0, le=1.0)
    assumptions: list[DealAssumption] = Field(default_factory=list)
    evidence_ids: list[str] = Field(default_factory=list)
    requested_template_key: str | None = None
    requested_payment_terms: str | None = None
    requested_discount_pct: float | None = None
    line_items: list[QuoteLineDraft] = Field(default_factory=list)
    approval_required: bool = True
    approval_reasoning: list[str] = Field(default_factory=list)

    @computed_field(alias="$schema")
    @property
    def schema(self) -> str:
        return "QuoteDraftPlan/v1"


class QuoteDraftPlanResult(_QuotePlanModel):
    quote_plan: QuoteDraftPlanV1
    execution_eligible: bool = False
    blocking_reasons: list[str] = Field(default_factory=list)
    readiness_mode: Literal["ready", "review_required", "unsupported", "insufficient_evidence"]


def build_quote_draft_plan(
    *,
    crm_org_id: str,
    opportunity_id: str,
    environment_profile: QuoteEnvironmentProfileV1,
    readiness: QuoteReadinessResult,
    context: DealContextV1,
    quote_request: dict[str, Any] | None = None,
    generated_at: str | None = None,
    allow_insufficient_evidence: bool = False,
) -> QuoteDraftPlanResult:
    payload = dict(quote_request or {})
    line_items, line_item_blockers = _line_items_from_request(payload.get("line_items"))
    blocking_reasons = _execution_blockers(
        environment_profile=environment_profile,
        readiness=readiness,
        line_item_blockers=line_item_blockers,
        line_items=line_items,
        allow_insufficient_evidence=allow_insufficient_evidence,
    )
    approval_reasoning = _approval_reasoning(
        readiness=readiness,
        blocking_reasons=blocking_reasons,
        allow_insufficient_evidence=allow_insufficient_evidence,
    )

    plan = QuoteDraftPlanV1(
        quote_request_id=_quote_request_id(opportunity_id, generated_at),
        crm_org_id=crm_org_id,
        opportunity_id=opportunity_id,
        engine="standard_quotes",
        confidence=float(readiness.overall_confidence),
        assumptions=[assumption.model_copy(deep=True) for assumption in context.assumptions],
        evidence_ids=list(context.evidence_ids),
        requested_template_key=_requested_value("template_key", payload, context),
        requested_payment_terms=_requested_value("payment_terms", payload, context),
        requested_discount_pct=_requested_discount(payload, context),
        line_items=line_items,
        approval_required=True,
        approval_reasoning=approval_reasoning,
    )
    return QuoteDraftPlanResult(
        quote_plan=plan,
        execution_eligible=not blocking_reasons,
        blocking_reasons=blocking_reasons,
        readiness_mode=readiness.readiness_mode,
    )


def _execution_blockers(
    *,
    environment_profile: QuoteEnvironmentProfileV1,
    readiness: QuoteReadinessResult,
    line_item_blockers: list[str],
    line_items: list[QuoteLineDraft],
    allow_insufficient_evidence: bool,
) -> list[str]:
    blockers: list[str] = []
    if environment_profile.detected_engine != "standard_quotes":
        blockers.append(
            "Tier 1 quote planning currently supports only orgs with detected_engine=standard_quotes."
        )
    if readiness.readiness_mode not in {"ready", "review_required"}:
        if not (
            readiness.readiness_mode == "insufficient_evidence" and allow_insufficient_evidence
        ):
            blockers.append(
                f"Quote readiness mode is {readiness.readiness_mode}; planning cannot proceed to execution."
            )
    for issue in readiness.issues:
        if issue.severity != "blocking":
            continue
        blockers.append(f"{issue.code}: {issue.message}")
    blockers.extend(line_item_blockers)
    if not line_items:
        blockers.append(
            "quote_request.line_items must include at least one explicit line item for v1 quote planning."
        )
    return _dedupe_strings(blockers)


def _approval_reasoning(
    *,
    readiness: QuoteReadinessResult,
    blocking_reasons: list[str],
    allow_insufficient_evidence: bool,
) -> list[str]:
    reasoning = ["Human approval remains required for v1 quote execution."]
    reasoning.append(f"Readiness mode is {readiness.readiness_mode}.")
    if readiness.recommended_approval_roles:
        reasoning.append(
            f"Recommended approver roles: {', '.join(readiness.recommended_approval_roles)}."
        )
    if readiness.readiness_mode == "insufficient_evidence" and allow_insufficient_evidence:
        reasoning.append(
            "Explicit allow_insufficient_evidence override was provided for controlled execution despite thin similarity evidence."
        )
    if blocking_reasons:
        reasoning.append("Execution remains blocked until all blocking reasons are cleared.")
    elif readiness.readiness_mode != "ready":
        reasoning.append("Execution requires explicit human override because readiness is not fully ready.")
    return reasoning


def _line_items_from_request(raw_items: Any) -> tuple[list[QuoteLineDraft], list[str]]:
    if raw_items in (None, ""):
        return [], [
            "Product inference is deferred in v1; provide quote_request.line_items with explicit product_id, product_code, or product_name."
        ]
    if not isinstance(raw_items, list):
        return [], ["quote_request.line_items must be a JSON array."]

    drafts: list[QuoteLineDraft] = []
    blockers: list[str] = []
    for index, item in enumerate(raw_items, start=1):
        if not isinstance(item, dict):
            blockers.append(f"quote_request.line_items[{index}] must be an object.")
            continue
        if not _has_product_reference(item):
            blockers.append(
                f"quote_request.line_items[{index}] must include product_id, product_code, or product_name."
            )
            continue
        try:
            drafts.append(QuoteLineDraft.model_validate(item))
        except ValidationError as exc:
            blockers.append(
                f"quote_request.line_items[{index}] is invalid: {exc.errors()[0].get('msg', 'validation failed')}."
            )
    return drafts, blockers


def _has_product_reference(item: dict[str, Any]) -> bool:
    for key in ("product_id", "product_code", "product_name"):
        value = item.get(key)
        if value not in (None, ""):
            return True
    return False


def _requested_value(
    key: str,
    payload: dict[str, Any],
    context: DealContextV1,
) -> str | None:
    value = payload.get(key)
    if value not in (None, ""):
        text = str(value).strip()
        if text:
            return text
    normalized = context.normalized_quote_request.get(key)
    if normalized not in (None, ""):
        text = str(normalized).strip()
        if text:
            return text
    for assumption in context.assumptions:
        if assumption.key != key or assumption.value in (None, ""):
            continue
        text = str(assumption.value).strip()
        if text:
            return text
    return None


def _requested_discount(payload: dict[str, Any], context: DealContextV1) -> float | None:
    candidate = payload.get("discount_pct", context.normalized_quote_request.get("discount_pct"))
    if candidate in (None, ""):
        return None
    try:
        return float(candidate)
    except Exception:
        return None


def _quote_request_id(opportunity_id: str, generated_at: str | None) -> str:
    if generated_at not in (None, ""):
        text = str(generated_at).strip()
        try:
            parsed = datetime.fromisoformat(text.replace("Z", "+00:00"))
        except Exception:
            parsed = None
        if parsed is not None:
            return f"qdr-{opportunity_id}-{parsed.astimezone(timezone.utc).strftime('%Y%m%dT%H%M%SZ')}"
    return f"qdr-{opportunity_id}-{datetime.now(timezone.utc).strftime('%Y%m%dT%H%M%SZ')}"


def _dedupe_strings(values: list[str]) -> list[str]:
    seen: set[str] = set()
    ordered: list[str] = []
    for value in values:
        text = str(value or "").strip()
        if not text or text in seen:
            continue
        ordered.append(text)
        seen.add(text)
    return ordered


__all__ = [
    "QuoteDraftPlanResult",
    "QuoteDraftPlanV1",
    "QuoteLineDraft",
    "build_quote_draft_plan",
]
