"""Historical deal forensics and pattern profile generation."""

from __future__ import annotations

from dataclasses import dataclass
from datetime import datetime, timezone
from statistics import median
from typing import Any, Iterable

from g_gremlin import sfdc as sfdc_helpers

from .config import DealDeskConfig
from .profiles import (
    ApprovalPattern,
    DealArchetype,
    DealMotionProfile,
    DealPatternProfileV1,
    PatternAnomalyRule,
    PatternUpdateEventV1,
    StageBenchmark,
)


_BASE_HISTORY_FIELDS: tuple[str, ...] = (
    "Id",
    "Name",
    "StageName",
    "IsClosed",
    "IsWon",
    "CloseDate",
    "CreatedDate",
    "LastModifiedDate",
    "Amount",
    "Type",
    "AccountId",
    "OwnerId",
)
_OPTIONAL_FIELD_TOKENS: tuple[str, ...] = (
    "segment",
    "product",
    "plan",
    "package",
    "edition",
    "sku",
    "discount",
    "term",
    "payment",
    "template",
    "approval",
    "duration",
    "arr",
    "acv",
    "tcv",
)
_EXACT_OPTIONAL_FIELDS: tuple[str, ...] = (
    "Pricebook2Id",
    "ContractTerm",
)
_OPTIONAL_FIELD_PRIORITIES: tuple[tuple[str, int], ...] = (
    ("segment", 0),
    ("product", 1),
    ("plan", 1),
    ("package", 1),
    ("edition", 1),
    ("sku", 1),
    ("payment", 2),
    ("term", 3),
    ("duration", 3),
    ("template", 4),
    ("discount", 5),
    ("approval", 6),
    ("arr", 7),
    ("acv", 7),
    ("tcv", 7),
)


@dataclass(frozen=True)
class NormalizedDealRecord:
    record_id: str
    motion_id: str
    motion_confidence: float
    segment: str | None
    product_family: str | None
    size_band: str
    discount_pct: float | None
    term_months: int | None
    payment_terms: str | None
    template_key: str | None
    approver_role: str | None
    approval_latency_hours: float | None
    stage_durations: dict[str, float]
    outcome: str
    evidence_id: str


def fetch_historical_opportunities(
    *,
    org_alias: str | None,
    lookback_days: int = 365,
    limit: int = 400,
) -> list[dict[str, Any]]:
    describe_payload = sfdc_helpers.describe_sobject("Opportunity", org_alias=org_alias)
    available_fields = _describe_field_names(describe_payload)
    selected_fields = list(_BASE_HISTORY_FIELDS)
    selected_fields.extend(suggest_opportunity_signal_fields(available_fields, limit=40))
    lookback_window = max(1, int(lookback_days))
    query = (
        f"SELECT {', '.join(selected_fields)} "
        "FROM Opportunity "
        "WHERE IsClosed = TRUE "
        f"AND CloseDate = LAST_N_DAYS:{lookback_window} "
        "ORDER BY CloseDate DESC "
        f"LIMIT {max(1, int(limit))}"
    )
    return list(sfdc_helpers.run_soql(query, org_alias=org_alias))


def build_deal_pattern_profile(
    *,
    crm_org_id: str,
    generated_at: str,
    records: list[dict[str, Any]],
    org_alias: str | None = None,
    config: DealDeskConfig | None = None,
    update_events: list[PatternUpdateEventV1] | None = None,
) -> DealPatternProfileV1:
    normalized_records = [normalize_deal_record(record, config=config) for record in records]
    normalized_records.extend(
        normalize_update_event(event, config=config) for event in (update_events or [])
    )
    normalized_records = [record for record in normalized_records if record.record_id]

    motion_profiles = _build_motion_profiles(normalized_records)
    archetypes = _build_archetypes(normalized_records)
    approval_patterns = _build_approval_patterns(normalized_records)
    stage_benchmarks = _build_stage_benchmarks(normalized_records)
    anomaly_rules = _build_anomaly_rules(archetypes)

    receipt_ids = sorted(
        {
            str(event.source_receipt_id)
            for event in (update_events or [])
            if event.source_receipt_id not in (None, "")
        }
    )
    learning_state = "history_only"
    if receipt_ids:
        learning_state = "history_plus_receipts"
    if receipt_ids and len(records) >= 25 and len(receipt_ids) >= 10:
        learning_state = "mature"

    profile_confidence = _profile_confidence(
        record_count=len(records),
        motion_count=len(motion_profiles),
        receipt_count=len(receipt_ids),
    )

    return DealPatternProfileV1(
        profile_id=f"dpp-{_safe_profile_key(crm_org_id, org_alias)}",
        crm_org_id=crm_org_id,
        org_alias=org_alias,
        generated_at=generated_at,
        updated_at=generated_at,
        analysis_window_days=365,
        profile_confidence=profile_confidence,
        learning_state=learning_state,
        motion_profiles=motion_profiles,
        archetypes=archetypes,
        approval_patterns=approval_patterns,
        stage_benchmarks=stage_benchmarks,
        anomaly_rules=anomaly_rules,
        evidence_ids=_top_values([record.evidence_id for record in normalized_records], limit=20),
        source_receipt_ids=receipt_ids,
        source_record_count=len(records),
        source_receipt_count=len(receipt_ids),
    )


def normalize_deal_record(
    record: dict[str, Any],
    *,
    config: DealDeskConfig | None = None,
) -> NormalizedDealRecord:
    motion_id, motion_confidence, _reason = infer_deal_motion(record)
    amount = extract_amount(record)
    return NormalizedDealRecord(
        record_id=str(record.get("Id") or "").strip(),
        motion_id=motion_id,
        motion_confidence=motion_confidence,
        segment=extract_segment(record),
        product_family=extract_product_family(record),
        size_band=resolve_size_band(amount, config=config),
        discount_pct=extract_discount_pct(record),
        term_months=extract_term_months(record),
        payment_terms=extract_payment_terms(record),
        template_key=extract_template_key(record),
        approver_role=extract_approver_role(record),
        approval_latency_hours=_extract_first_float(
            record,
            ("Approval_Latency_Hours__c", "ApprovalDurationHours__c"),
        ),
        stage_durations=extract_stage_durations(record),
        outcome=infer_outcome(record),
        evidence_id=str(record.get("Id") or "").strip(),
    )


def normalize_update_event(
    event: PatternUpdateEventV1,
    *,
    config: DealDeskConfig | None = None,
) -> NormalizedDealRecord:
    amount = _extract_numeric_from_custom(event.extracted_signals.custom, ("amount", "tcv", "acv"))
    motion_id = str(event.extracted_motion_id or "").strip() or "unknown"
    return NormalizedDealRecord(
        record_id=str(event.opportunity_id or event.event_id).strip(),
        motion_id=motion_id,
        motion_confidence=float(event.confidence),
        segment=_extract_text_from_custom(event.extracted_signals.custom, ("segment",)),
        product_family=_extract_text_from_custom(event.extracted_signals.custom, ("product_family", "product")),
        size_band=resolve_size_band(amount, config=config),
        discount_pct=event.extracted_signals.discount_pct,
        term_months=event.extracted_signals.term_months,
        payment_terms=event.extracted_signals.payment_terms,
        template_key=event.extracted_signals.template_key,
        approver_role=(event.extracted_signals.approver_roles or [None])[0],
        approval_latency_hours=event.extracted_signals.approval_latency_hours,
        stage_durations=dict(event.extracted_signals.stage_durations),
        outcome=str(event.extracted_signals.outcome or "unknown"),
        evidence_id=str(event.source_ref or event.event_id).strip(),
    )


def infer_deal_motion(record: dict[str, Any]) -> tuple[str, float, str]:
    motion_hint = " ".join(
        str(record.get(field) or "")
        for field in ("Type", "Deal_Motion__c", "Opportunity_Type__c", "Name")
    ).lower()
    if any(token in motion_hint for token in ("renewal", "re-sign", "resign")):
        return "renewal", 0.95, "renewal token match"
    if any(token in motion_hint for token in ("upsell", "cross-sell", "cross sell", "expansion", "add-on")):
        return "expansion", 0.92, "expansion token match"
    if any(token in motion_hint for token in ("downsell", "contraction", "churn")):
        return "churn", 0.9, "contraction token match"
    if any(token in motion_hint for token in ("new business", "new logo", "new customer")):
        return "new_logo", 0.85, "new business token match"
    if _coerce_bool(record.get("IsWon")):
        return "new_logo", 0.55, "won default"
    return "unknown", 0.35, "insufficient motion evidence"


def infer_outcome(record: dict[str, Any]) -> str:
    if _coerce_bool(record.get("IsWon")):
        return "closed_won"
    if _coerce_bool(record.get("IsClosed")):
        return "closed_lost"
    return "open"


def extract_amount(record: dict[str, Any]) -> float | None:
    return _extract_first_float(record, ("Amount", "TCV__c", "ACV__c", "ARR__c"))


def extract_discount_pct(record: dict[str, Any]) -> float | None:
    raw = _extract_first_float(
        record,
        (
            "Discount__c",
            "DiscountPercent",
            "Discount_Pct__c",
            "Overall_Discount__c",
            "Partner_Discount__c",
            "Discount",
        ),
    )
    if raw is None:
        raw = _extract_hint_float(
            record,
            phrases=("discount percent", "discount pct", "discount"),
        )
    if raw is None:
        return None
    if abs(raw) <= 1.0:
        return raw * 100.0
    return raw


def extract_term_months(record: dict[str, Any]) -> int | None:
    raw = _extract_first_float(
        record,
        (
            "ContractTerm",
            "Term_Months__c",
            "SubscriptionTerm__c",
            "Duration_Months__c",
            "Term__c",
        ),
    )
    if raw is None:
        raw = _extract_hint_float(
            record,
            phrases=("contract term", "subscription term", "duration months", "term months", "term", "months"),
            excluded_phrases=("payment", "billing", "notice"),
        )
    if raw is None:
        return None
    return int(round(raw))


def extract_segment(record: dict[str, Any]) -> str | None:
    return _extract_first_text(
        record,
        (
            "Segment__c",
            "Market_Segment__c",
            "Customer_Segment__c",
            "Business_Segment__c",
            "Segment",
        ),
    ) or _extract_hint_text(
        record,
        phrases=("market segment", "customer segment", "business segment", "segment"),
    )


def extract_product_family(record: dict[str, Any]) -> str | None:
    return _extract_first_text(
        record,
        (
            "Product_Family__c",
            "Product_Line__c",
            "Primary_Product__c",
            "Product__c",
            "PrimaryProduct__c",
            "Product_Type__c",
            "Plan_Type__c",
            "Package__c",
            "Edition__c",
            "SKU_Family__c",
        ),
    ) or _extract_hint_text(
        record,
        phrases=("product family", "product line", "primary product", "product type", "plan type", "package", "edition", "sku"),
    )


def extract_payment_terms(record: dict[str, Any]) -> str | None:
    return _extract_first_text(
        record,
        (
            "Payment_Terms__c",
            "PaymentTerms__c",
            "Payment_Term__c",
            "Billing_Terms__c",
            "Billing_Term__c",
            "Terms_For_Payment__c",
            "Terms_Of_Payment__c",
        ),
    ) or _extract_hint_text(
        record,
        phrases=(
            "terms for payment",
            "terms of payment",
            "payment terms",
            "payment term",
            "billing terms",
            "billing term",
            "invoice terms",
            "invoice term",
        ),
    )


def extract_template_key(record: dict[str, Any]) -> str | None:
    return _extract_first_text(
        record,
        ("Template__c", "Quote_Template__c", "Document_Template__c", "Template_Key__c", "SBQQ__Template__c"),
    ) or _extract_hint_text(
        record,
        phrases=("quote template", "document template", "template key", "template"),
    )


def extract_approver_role(record: dict[str, Any]) -> str | None:
    return _extract_first_text(
        record,
        ("Approval_Role__c", "Approver_Role__c", "Approved_By_Role__c", "ApproverRole__c", "Approval_Owner_Role__c"),
    ) or _extract_hint_text(
        record,
        phrases=("approval owner role", "approved by role", "approver role", "approval role"),
    )


def extract_stage_durations(record: dict[str, Any]) -> dict[str, float]:
    durations: dict[str, float] = {}
    for field_name, raw_value in record.items():
        value = _coerce_float(raw_value)
        if value is None:
            continue
        lowered = str(field_name or "").lower()
        if lowered.startswith("stage_duration_"):
            stage_name = field_name[len("Stage_Duration_") :]
        elif lowered.endswith("_duration_hours__c"):
            stage_name = field_name[: -len("_Duration_Hours__c")]
        else:
            continue
        cleaned = str(stage_name).replace("__c", "").replace("_", " ").strip()
        if cleaned:
            durations[cleaned] = value
    return durations


def resolve_size_band(amount: float | None, *, config: DealDeskConfig | None = None) -> str:
    if config is not None and config.size_bands:
        return config.resolve_size_band(amount)
    if amount is None:
        return "unknown"
    if amount < 25000:
        return "small"
    if amount < 100000:
        return "mid_market"
    return "enterprise"


def _build_motion_profiles(records: list[NormalizedDealRecord]) -> list[DealMotionProfile]:
    grouped: dict[str, list[NormalizedDealRecord]] = {}
    for record in records:
        grouped.setdefault(record.motion_id, []).append(record)

    profiles: list[DealMotionProfile] = []
    for motion_id, rows in sorted(grouped.items()):
        profiles.append(
            DealMotionProfile(
                motion_id=motion_id,
                label=_humanize_motion(motion_id),
                confidence=round(sum(row.motion_confidence for row in rows) / max(1, len(rows)), 4),
                heuristic_summary="Inferred from historical opportunity shape and naming signals.",
                observed_deal_count=len(rows),
                common_products=_top_values([row.product_family for row in rows if row.product_family], limit=4),
                common_segments=_top_values([row.segment for row in rows if row.segment], limit=4),
                size_bands=_top_values([row.size_band for row in rows if row.size_band], limit=4),
                evidence_ids=_top_values([row.evidence_id for row in rows], limit=5),
            )
        )
    return profiles


def _build_archetypes(records: list[NormalizedDealRecord]) -> list[DealArchetype]:
    grouped: dict[tuple[str, str, str, str], list[NormalizedDealRecord]] = {}
    for record in records:
        key = (
            record.motion_id,
            record.segment or "unknown",
            record.product_family or "unknown",
            record.size_band or "unknown",
        )
        grouped.setdefault(key, []).append(record)

    archetypes: list[DealArchetype] = []
    for (motion_id, segment, product_family, size_band), rows in sorted(grouped.items()):
        discounts = [row.discount_pct for row in rows if row.discount_pct is not None]
        terms = [row.term_months for row in rows if row.term_months is not None]
        payment_terms = _top_values([row.payment_terms for row in rows if row.payment_terms], limit=1)
        archetypes.append(
            DealArchetype(
                archetype_id=(
                    f"{motion_id}:{segment}:{product_family}:{size_band}".replace(" ", "_").lower()
                ),
                motion_id=motion_id,
                label=_archetype_label(segment, product_family, size_band),
                segment=None if segment == "unknown" else segment,
                product_family=None if product_family == "unknown" else product_family,
                size_band=None if size_band == "unknown" else size_band,
                typical_discount_pct=_median_or_none(discounts),
                p90_discount_pct=_percentile_or_none(discounts, 0.9),
                typical_term_months=int(round(_median_or_none(terms) or 0)) or None,
                typical_payment_terms=payment_terms[0] if payment_terms else None,
                common_templates=_top_values([row.template_key for row in rows if row.template_key], limit=3),
                common_approver_roles=_top_values([row.approver_role for row in rows if row.approver_role], limit=3),
                observed_deal_count=len(rows),
                confidence=min(0.97, round(0.42 + (min(len(rows), 20) * 0.025), 4)),
                evidence_ids=_top_values([row.evidence_id for row in rows], limit=6),
            )
        )
    return archetypes


def _build_approval_patterns(records: list[NormalizedDealRecord]) -> list[ApprovalPattern]:
    grouped: dict[tuple[str, str], list[NormalizedDealRecord]] = {}
    for record in records:
        key = (record.motion_id, record.segment or "unknown")
        grouped.setdefault(key, []).append(record)

    patterns: list[ApprovalPattern] = []
    for (motion_id, segment), rows in sorted(grouped.items()):
        discounts = [row.discount_pct for row in rows if row.discount_pct is not None]
        latencies = [row.approval_latency_hours for row in rows if row.approval_latency_hours is not None]
        approver_roles = _top_values([row.approver_role for row in rows if row.approver_role], limit=4)
        patterns.append(
            ApprovalPattern(
                pattern_id=f"{motion_id}:{segment}:approval".replace(" ", "_").lower(),
                motion_id=motion_id,
                segment=None if segment == "unknown" else segment,
                min_discount_pct=min(discounts) if discounts else None,
                max_discount_pct=max(discounts) if discounts else None,
                approver_roles=approver_roles,
                median_approval_hours=_median_or_none(latencies),
                override_rate=None,
                observed_count=len(rows),
                confidence=min(0.95, round(0.4 + (min(len(rows), 20) * 0.02), 4)),
                evidence_ids=_top_values([row.evidence_id for row in rows], limit=5),
            )
        )
    return patterns


def _build_stage_benchmarks(records: list[NormalizedDealRecord]) -> list[StageBenchmark]:
    grouped: dict[tuple[str, str], list[float]] = {}
    counts: dict[tuple[str, str], int] = {}
    for record in records:
        for stage_name, hours in record.stage_durations.items():
            key = (record.motion_id, stage_name)
            grouped.setdefault(key, []).append(hours)
            counts[key] = counts.get(key, 0) + 1

    benchmarks: list[StageBenchmark] = []
    for (motion_id, stage_name), values in sorted(grouped.items()):
        benchmarks.append(
            StageBenchmark(
                stage_name=stage_name,
                motion_id=motion_id,
                median_hours_in_stage=_median_or_none(values),
                p90_hours_in_stage=_percentile_or_none(values, 0.9),
                skip_rate=None,
                stall_rate=None,
                observed_count=counts.get((motion_id, stage_name), len(values)),
            )
        )
    return benchmarks


def _build_anomaly_rules(archetypes: list[DealArchetype]) -> list[PatternAnomalyRule]:
    rules: list[PatternAnomalyRule] = []
    for archetype in archetypes:
        if archetype.p90_discount_pct is not None:
            rules.append(
                PatternAnomalyRule(
                    rule_id=f"{archetype.archetype_id}:discount_above_p90",
                    label=f"{archetype.label} discount above p90",
                    description=(
                        "Flag discounts that exceed the historical p90 for this archetype "
                        f"({archetype.p90_discount_pct:.2f}%)."
                    ),
                    severity="high",
                    trigger_summary=f"discount_pct > {archetype.p90_discount_pct:.2f}",
                )
            )
    return rules


def _describe_field_names(describe_payload: dict[str, Any]) -> set[str]:
    fields: list[dict[str, Any]] = []
    if isinstance(describe_payload.get("result"), dict):
        fields = [
            item
            for item in describe_payload.get("result", {}).get("fields", [])
            if isinstance(item, dict)
        ]
    elif isinstance(describe_payload.get("fields"), list):
        fields = [item for item in describe_payload.get("fields", []) if isinstance(item, dict)]
    return {str(item.get("name") or "").strip() for item in fields if item.get("name")}


def suggest_opportunity_signal_fields(
    available_fields: Iterable[str],
    *,
    limit: int = 24,
) -> list[str]:
    candidates: list[tuple[int, str, str]] = []
    seen: set[str] = set()
    for raw_field_name in available_fields:
        field_name = str(raw_field_name or "").strip()
        if not field_name or field_name in seen or field_name in _BASE_HISTORY_FIELDS:
            continue
        priority = _optional_field_priority(field_name)
        if priority is None:
            continue
        candidates.append((priority, field_name.lower(), field_name))
        seen.add(field_name)
    candidates.sort(key=lambda item: (item[0], item[1]))
    return [field_name for _priority, _lowered, field_name in candidates[: max(1, int(limit))]]


def _extract_first_text(record: dict[str, Any], field_names: Iterable[str]) -> str | None:
    for field_name in field_names:
        value = record.get(field_name)
        if value in (None, ""):
            continue
        text = str(value).strip()
        if text:
            return text
    return None


def _extract_first_float(record: dict[str, Any], field_names: Iterable[str]) -> float | None:
    for field_name in field_names:
        value = _coerce_float(record.get(field_name))
        if value is not None:
            return value
    return None


def _extract_hint_text(
    record: dict[str, Any],
    *,
    phrases: Iterable[str],
    excluded_phrases: Iterable[str] = (),
) -> str | None:
    best_field = _best_matching_field(record, phrases=phrases, excluded_phrases=excluded_phrases)
    if best_field is None:
        return None
    value = record.get(best_field)
    if value in (None, ""):
        return None
    text = str(value).strip()
    return text or None


def _extract_hint_float(
    record: dict[str, Any],
    *,
    phrases: Iterable[str],
    excluded_phrases: Iterable[str] = (),
) -> float | None:
    best_field = _best_matching_field(record, phrases=phrases, excluded_phrases=excluded_phrases)
    if best_field is None:
        return None
    return _coerce_float(record.get(best_field))


def _best_matching_field(
    record: dict[str, Any],
    *,
    phrases: Iterable[str],
    excluded_phrases: Iterable[str],
) -> str | None:
    normalized_phrases = tuple(str(phrase).strip().lower() for phrase in phrases if str(phrase).strip())
    excluded = tuple(str(phrase).strip().lower() for phrase in excluded_phrases if str(phrase).strip())
    candidates: list[tuple[int, int, str, str]] = []
    for field_name, value in record.items():
        if value in (None, ""):
            continue
        normalized_name = _normalize_field_name(field_name)
        if excluded and any(phrase in normalized_name for phrase in excluded):
            continue
        score = max((len(phrase.split()) for phrase in normalized_phrases if phrase in normalized_name), default=0)
        if score <= 0:
            continue
        candidates.append((-score, len(str(field_name)), str(field_name).lower(), str(field_name)))
    if not candidates:
        return None
    candidates.sort()
    return candidates[0][3]


def _extract_text_from_custom(custom: dict[str, Any], keys: Iterable[str]) -> str | None:
    for key in keys:
        value = custom.get(key)
        if value in (None, ""):
            continue
        text = str(value).strip()
        if text:
            return text
    return None


def _extract_numeric_from_custom(custom: dict[str, Any], keys: Iterable[str]) -> float | None:
    for key in keys:
        value = _coerce_float(custom.get(key))
        if value is not None:
            return value
    return None


def _coerce_float(value: Any) -> float | None:
    if value in (None, ""):
        return None
    try:
        return float(str(value).replace(",", "").strip())
    except Exception:
        return None


def _normalize_field_name(field_name: Any) -> str:
    text = str(field_name or "").strip().lower()
    return " ".join(text.replace("__c", " ").replace("_", " ").replace(".", " ").split())


def _optional_field_priority(field_name: str) -> int | None:
    if field_name in _EXACT_OPTIONAL_FIELDS:
        return -1
    lowered = str(field_name or "").lower()
    priorities = [priority for token, priority in _OPTIONAL_FIELD_PRIORITIES if token in lowered]
    if not priorities:
        return None
    return min(priorities)


def _coerce_bool(value: Any) -> bool:
    text = str(value or "").strip().lower()
    return text in {"true", "1", "yes"}


def _median_or_none(values: list[float | int]) -> float | None:
    if not values:
        return None
    return float(median(values))


def _percentile_or_none(values: list[float | int], percentile: float) -> float | None:
    if not values:
        return None
    ordered = sorted(float(value) for value in values)
    index = int(round((len(ordered) - 1) * max(0.0, min(percentile, 1.0))))
    return ordered[index]


def _top_values(values: Iterable[str | None], *, limit: int) -> list[str]:
    counts: dict[str, int] = {}
    for value in values:
        text = str(value or "").strip()
        if not text:
            continue
        counts[text] = counts.get(text, 0) + 1
    ordered = sorted(counts.items(), key=lambda item: (-item[1], item[0].lower()))
    return [value for value, _count in ordered[:limit]]


def _humanize_motion(motion_id: str) -> str:
    text = str(motion_id or "").strip().replace("_", " ")
    return " ".join(part.capitalize() for part in text.split()) or "Unknown"


def _archetype_label(segment: str, product_family: str, size_band: str) -> str:
    parts = [part for part in (segment, product_family, size_band) if part and part != "unknown"]
    if not parts:
        return "General archetype"
    return " ".join(parts)


def _profile_confidence(*, record_count: int, motion_count: int, receipt_count: int) -> float:
    base = 0.25
    history_bonus = min(record_count, 80) / 160.0
    diversity_bonus = min(motion_count, 4) * 0.05
    receipt_bonus = min(receipt_count, 10) * 0.02
    return min(0.97, round(base + history_bonus + diversity_bonus + receipt_bonus, 4))


def _safe_profile_key(crm_org_id: str, org_alias: str | None) -> str:
    key = str(crm_org_id or "").strip() or "unknown-org"
    alias = str(org_alias or "").strip()
    if alias:
        return f"{key}-{alias}".replace(" ", "-")
    return key.replace(" ", "-")


__all__ = [
    "NormalizedDealRecord",
    "build_deal_pattern_profile",
    "extract_amount",
    "extract_discount_pct",
    "extract_payment_terms",
    "extract_product_family",
    "extract_segment",
    "extract_stage_durations",
    "extract_template_key",
    "extract_term_months",
    "fetch_historical_opportunities",
    "infer_deal_motion",
    "infer_outcome",
    "normalize_deal_record",
    "normalize_update_event",
    "resolve_size_band",
    "suggest_opportunity_signal_fields",
]
