"""Policy helpers for quote revision preview scoring and apply support."""

from __future__ import annotations

from typing import Any, Callable


def _dedupe_nonempty_strings(values: list[str], *, limit: int = 6) -> list[str]:
    deduped: list[str] = []
    seen: set[str] = set()
    for value in values:
        normalized = str(value or "").strip()
        if not normalized:
            continue
        key = normalized.lower()
        if key in seen:
            continue
        seen.add(key)
        deduped.append(normalized)
    return deduped[:limit]


def build_quote_preview_policy_reasons(
    *,
    quote_status: Any,
    preview_readiness: Any | None,
    preview_readiness_error: str | None,
    is_quote_active: Callable[[str | None], bool],
) -> list[str]:
    reasons: list[str] = []
    selected_quote = getattr(quote_status, "selected_quote", None)
    if selected_quote is not None and not is_quote_active(getattr(selected_quote, "status", None)):
        reasons.append(
            f"Selected quote `{selected_quote.label}` is in terminal status `{selected_quote.status}`."
        )
    reasons.extend(list(getattr(quote_status, "conflict_reasons", []) or [])[:2])
    reasons.extend(list(getattr(quote_status, "stale_reasons", []) or [])[:2])
    if getattr(quote_status, "approval_state", None) == "pending":
        reasons.append("The current quote already has an approval process in flight.")
    matched_routes = list(getattr(quote_status, "matched_approval_route_names", []) or [])
    if matched_routes:
        reasons.append("Current deal shape matches approval route(s): " + ", ".join(matched_routes) + ".")
    if preview_readiness is not None:
        warning_messages = [
            issue.message
            for issue in getattr(preview_readiness, "issues", []) or []
            if getattr(issue, "severity", None) in {"blocking", "warning"}
        ]
        reasons.extend(warning_messages[:3])
        recommended_roles = list(getattr(preview_readiness, "recommended_approval_roles", []) or [])
        if recommended_roles:
            reasons.append("Preview suggests approver role(s): " + ", ".join(recommended_roles) + ".")
    elif preview_readiness_error:
        reasons.append(f"Preview readiness analysis was unavailable: {preview_readiness_error}")
    return _dedupe_nonempty_strings(reasons)


def build_quote_line_item_preview_policy_reasons(
    *,
    quote_status: Any,
    preview_readiness: Any | None,
    preview_readiness_error: str | None,
    is_quote_active: Callable[[str | None], bool],
) -> list[str]:
    reasons = build_quote_preview_policy_reasons(
        quote_status=quote_status,
        preview_readiness=preview_readiness,
        preview_readiness_error=preview_readiness_error,
        is_quote_active=is_quote_active,
    )
    reasons.append(
        "Line-item policy coverage is still narrow; Gremlin is relying on the current line diff and exact SKU resolution for this guided change."
    )
    return _dedupe_nonempty_strings(reasons)


def build_quote_line_item_preview_next_step(
    *,
    operation: str,
    quote_status: Any,
    preview_policy_state: str,
    preview_readiness: Any | None,
    preview_readiness_error: str | None,
    apply_supported: bool,
    apply_support_reason: str,
    is_quote_active: Callable[[str | None], bool],
) -> str:
    selected_quote = getattr(quote_status, "selected_quote", None)
    if selected_quote is not None and not is_quote_active(getattr(selected_quote, "status", None)):
        return "Preview only in this slice. The selected quote is already in a terminal status, so line-item changes should stay in the native tool."
    if bool(getattr(quote_status, "conflict", False)):
        return "Preview only in this slice. Resolve the conflicting quote evidence with RevOps before relying on a line-item change."
    if bool(getattr(quote_status, "stale", False)):
        return "Preview only in this slice. Refresh the stale quote in the native tool before relying on this line-item diff."
    if not apply_supported:
        return (
            apply_support_reason
            or "Preview only in this slice. Guided line-item apply is not enabled for this quote stack yet."
        )
    if preview_policy_state == "review_required":
        if str(preview_readiness_error or "").strip():
            return (
                "Preview only in this slice. Preview readiness analysis was unavailable, so keep this "
                "change in review or make it in the native quote tool."
            )
        if preview_readiness is not None and getattr(preview_readiness, "readiness_mode", None) != "ready":
            return (
                "Preview only in this slice. Review the highlighted readiness and policy signals before "
                "changing the quote in the native tool."
            )
        return (
            "Preview only in this slice. Review the quote status and approval context before making "
            "the line-item change in the native tool."
        )
    if operation == "remove_product":
        return "If this removal diff is correct, request approval to remove the line from the current standard quote."
    if preview_policy_state == "approval_likely":
        return "If this line-item diff is correct, request approval to apply it on the current standard quote."
    if preview_policy_state == "clean":
        return "If this line-item diff is correct, you can apply it directly from Slack on the current standard quote."
    if operation == "add_product":
        return "Use this SKU-resolution diff to confirm the exact product before applying it on the current standard quote."
    return "Use this quantity diff to confirm the exact current line before applying it on the current standard quote."


def build_quote_preview_policy_state(
    *,
    quote_status: Any,
    preview_readiness: Any | None,
    preview_readiness_error: str | None,
    is_quote_active: Callable[[str | None], bool],
) -> str:
    if getattr(quote_status, "status", None) in {"unsupported", "ambiguous", "no_quote"}:
        return "unsupported"
    selected_quote = getattr(quote_status, "selected_quote", None)
    if selected_quote is not None and not is_quote_active(getattr(selected_quote, "status", None)):
        return "review_required"
    if bool(getattr(quote_status, "conflict", False)):
        return "review_required"
    if preview_readiness is not None and any(
        getattr(issue, "severity", None) == "blocking"
        for issue in getattr(preview_readiness, "issues", []) or []
    ):
        return "review_required"
    if preview_readiness is None and str(preview_readiness_error or "").strip():
        return "review_required"
    if bool(getattr(quote_status, "stale", False)):
        return "review_required"
    if preview_readiness is not None and (
        getattr(preview_readiness, "readiness_mode", None) in {"review_required", "unsupported", "insufficient_evidence"}
        or any(getattr(issue, "severity", None) == "warning" for issue in getattr(preview_readiness, "issues", []) or [])
    ):
        return "approval_likely"
    if getattr(quote_status, "approval_state", None) in {"pending", "not_requested"} or list(
        getattr(quote_status, "matched_approval_route_names", []) or []
    ):
        return "approval_likely"
    return "clean"


def build_quote_preview_next_step(
    *,
    apply_supported: bool,
    apply_support_reason: str,
    preview_policy_state: str,
    quote_status: Any,
    preview_readiness: Any | None,
    preview_readiness_error: str | None,
    is_quote_active: Callable[[str | None], bool],
) -> str:
    selected_quote = getattr(quote_status, "selected_quote", None)
    if selected_quote is not None and not is_quote_active(getattr(selected_quote, "status", None)):
        return "Preview only in this slice. The selected quote is already in a terminal status, so guided header updates should not run on it."
    if bool(getattr(quote_status, "conflict", False)):
        return "Preview is available, but the current quote has conflicting evidence. Ask RevOps to reconcile the quote before guided revision is enabled."
    if bool(getattr(quote_status, "stale", False)):
        return "Preview is available, but the current quote is stale. Refresh the quote in the native tool before relying on guided revision."
    if (
        not apply_supported
        and str(apply_support_reason or "").strip()
        and selected_quote is not None
        and str(getattr(selected_quote, "object_name", "") or "").strip() == "quotes"
        and str(getattr(selected_quote, "status", "") or "").strip().upper() != "DRAFT"
    ):
        return (
            f"{apply_support_reason} Use the native HubSpot quote flow to edit, clone, or re-draft "
            "the quote before relying on guided apply."
        )
    if apply_supported and preview_policy_state == "approval_likely":
        return "If this field diff is correct, request approval to apply it on the current quote."
    if apply_supported and preview_policy_state == "clean":
        return "If this field diff is correct, you can apply it directly from Slack on the current quote."
    if preview_policy_state == "approval_likely":
        return "Preview only in this slice. This change likely needs approval or RevOps review once guided apply is enabled."
    if preview_policy_state == "review_required":
        if str(preview_readiness_error or "").strip():
            return (
                "Preview only in this slice. Preview readiness analysis was unavailable, so keep this "
                "change in review or make it in the native quote tool."
            )
        if preview_readiness is not None and getattr(preview_readiness, "readiness_mode", None) != "ready":
            return "Preview only in this slice. Review the highlighted policy and readiness signals before changing the quote in the native tool."
        return "Preview only in this slice. Review the quote status and approval context before making the change in the native tool."
    return "Preview only in this slice. No quote changes were applied; use this preview to validate the edit before updating the native quote."


def build_quote_header_apply_support(
    *,
    capability_profile: Any | None,
    detected_engine: str,
    adapter: Any | None = None,
    adapter_apply_supported: bool | None = None,
    adapter_resolver: Callable[..., Any] | None = None,
) -> tuple[bool, str]:
    if capability_profile is None:
        if adapter_apply_supported is False:
            return True, ""
        resolved_adapter = adapter or (
            adapter_resolver(detected_engine=detected_engine) if adapter_resolver is not None else None
        )
        if resolved_adapter is not None and resolved_adapter.allow_header_apply_without_capability_profile():
            return True, ""
        return (
            False,
            "Guided quote apply for this quote stack remains preview-only until capability discovery verifies `update_quote` support.",
        )
    supported_capabilities = set(getattr(capability_profile, "supported_capabilities", []) or [])
    if "update_quote" not in supported_capabilities:
        return (
            False,
            "Guided quote apply is not available because this stack does not advertise `update_quote` support.",
        )
    execution_verified_capabilities = set(getattr(capability_profile, "execution_verified_capabilities", []) or [])
    if "update_quote" in execution_verified_capabilities:
        return True, ""
    if detected_engine == "salesforce_cpq":
        return (
            False,
            "Guided quote apply for Salesforce CPQ remains preview-only in this slice. Gremlin can show the header diff, but quote mutation is not execution-verified yet.",
        )
    return (
        False,
        "Guided quote apply for this quote stack remains preview-only because `update_quote` is not execution-verified yet.",
    )


def build_quote_line_item_apply_support(
    *,
    capability_profile: Any | None,
    detected_engine: str,
    adapter: Any | None = None,
    adapter_apply_supported: bool | None = None,
    adapter_resolver: Callable[..., Any] | None = None,
) -> tuple[bool, str]:
    if capability_profile is None:
        if adapter_apply_supported is False:
            return True, ""
        resolved_adapter = adapter or (
            adapter_resolver(detected_engine=detected_engine) if adapter_resolver is not None else None
        )
        if resolved_adapter is not None and resolved_adapter.allow_line_item_apply_without_capability_profile():
            return True, ""
        return (
            False,
            "Guided line-item apply for this quote stack remains preview-only until capability discovery verifies `update_quote` support.",
        )
    supported_capabilities = set(getattr(capability_profile, "supported_capabilities", []) or [])
    if "update_quote" not in supported_capabilities:
        return (
            False,
            "Guided line-item apply is not available because this stack does not advertise `update_quote` support.",
        )
    execution_verified_capabilities = set(getattr(capability_profile, "execution_verified_capabilities", []) or [])
    if "update_quote" in execution_verified_capabilities:
        return True, ""
    return (
        False,
        "Guided line-item apply for this quote stack remains preview-only because `update_quote` is not execution-verified yet.",
    )


def build_quote_header_apply_capability_gaps(
    *,
    capability_profile: Any | None,
    apply_supported: bool,
) -> list[str]:
    if capability_profile is None or apply_supported:
        return []
    supported_capabilities = set(getattr(capability_profile, "supported_capabilities", []) or [])
    if "update_quote" not in supported_capabilities:
        return []
    gaps = [
        "`update_quote` remains inference-only on this stack, so guided header apply stays preview-only.",
    ]
    execution_verified = list(getattr(capability_profile, "execution_verified_capabilities", []) or [])
    if execution_verified:
        gaps.append(
            "Execution-verified today: "
            + ", ".join(f"`{item}`" for item in execution_verified[:4])
            + ("." if len(execution_verified) <= 4 else ", ...")
        )
    return gaps


def build_quote_line_item_apply_capability_gaps(
    *,
    capability_profile: Any | None,
    apply_supported: bool,
) -> list[str]:
    if capability_profile is None or apply_supported:
        return []
    supported_capabilities = set(getattr(capability_profile, "supported_capabilities", []) or [])
    if "update_quote" not in supported_capabilities:
        return []
    gaps = [
        "`update_quote` remains inference-only on this stack, so guided line-item apply stays preview-only.",
    ]
    execution_verified = list(getattr(capability_profile, "execution_verified_capabilities", []) or [])
    if execution_verified:
        gaps.append(
            "Execution-verified today: "
            + ", ".join(f"`{item}`" for item in execution_verified[:4])
            + ("." if len(execution_verified) <= 4 else ", ...")
        )
    return gaps


def build_quote_revision_apply_support(
    *,
    preview: Any,
    capability_profile: Any | None,
    adapter_resolver: Callable[..., Any],
) -> tuple[bool, str]:
    adapter = adapter_resolver(detected_engine=preview.detected_engine)
    if adapter is None:
        return False, "Guided quote apply is not available for this quote stack."
    adapter_supported, adapter_reason = adapter.preview_apply_support(preview=preview)
    if getattr(preview, "line_item_preview", None) is not None:
        line_item_supported, line_item_reason = build_quote_line_item_apply_support(
            capability_profile=capability_profile,
            detected_engine=preview.detected_engine,
            adapter=adapter,
            adapter_apply_supported=adapter_supported,
            adapter_resolver=adapter_resolver,
        )
        if not line_item_supported:
            return False, line_item_reason
        return adapter_supported, adapter_reason
    header_supported, header_reason = build_quote_header_apply_support(
        capability_profile=capability_profile,
        detected_engine=preview.detected_engine,
        adapter=adapter,
        adapter_apply_supported=adapter_supported,
        adapter_resolver=adapter_resolver,
    )
    if not header_supported:
        return False, header_reason
    return adapter_supported, adapter_reason


def build_quote_revision_apply_support_update(
    *,
    preview: Any,
    capability_profile: Any | None,
    adapter_resolver: Callable[..., Any],
) -> dict[str, Any]:
    apply_supported, apply_support_reason = build_quote_revision_apply_support(
        preview=preview,
        capability_profile=capability_profile,
        adapter_resolver=adapter_resolver,
    )
    return {
        "apply_supported": apply_supported,
        "apply_support_reason": apply_support_reason,
    }


def build_quote_revision_risk_level(preview_policy_state: str) -> int:
    return 0 if preview_policy_state == "clean" else 1
