"""Quote intent parsing and normalization helpers for Deal Desk."""

from __future__ import annotations

import json
import re
from datetime import date, datetime, timezone
from typing import Any, Literal

from pydantic import BaseModel, ConfigDict, computed_field


QuoteIntentOperation = Literal[
    "update_discount_pct",
    "update_term_months",
    "update_payment_terms",
    "update_quote_title",
    "update_expiration_date",
    "update_purchase_order_number",
    "add_product",
    "remove_product",
    "update_quantity",
]
QuoteIntentSemanticKey = Literal[
    "discount_pct",
    "term_months",
    "payment_terms",
    "quote_title",
    "expiration_date",
    "purchase_order_number",
    "line_item",
]
QuoteLineReferenceType = Literal["product_id", "product_code", "product_name", "line_item_id"]

_SELLER_PRODUCT_ID_RE = re.compile(r"^(?:01t|01u|01v)[a-zA-Z0-9]{12,15}$", re.IGNORECASE)


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

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

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> "_QuoteIntentModel":
        return cls.model_validate(data)


class QuoteIntentV1(_QuoteIntentModel):
    version: int = 1
    opportunity_id: str
    crm_org_id: str
    org_alias: str | None = None
    raw_request: str = ""
    operation: QuoteIntentOperation
    semantic_key: QuoteIntentSemanticKey
    target_label: str
    requested_value_raw: str
    requested_value_display: str
    requested_discount_pct: float | None = None
    requested_term_months: int | None = None
    requested_payment_terms: str | None = None
    requested_quote_title: str | None = None
    requested_expiration_date: str | None = None
    requested_purchase_order_number: str | None = None
    requested_product_reference: str | None = None
    requested_product_reference_type: QuoteLineReferenceType | None = None
    requested_quantity: float | None = None

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


def _normalize_quote_intent_operation(value: QuoteIntentOperation | str) -> QuoteIntentOperation:
    normalized = str(value or "").strip().lower()
    aliases = {
        "discount": "update_discount_pct",
        "discount_pct": "update_discount_pct",
        "discount_percent": "update_discount_pct",
        "term": "update_term_months",
        "term_months": "update_term_months",
        "payment_terms": "update_payment_terms",
        "payment": "update_payment_terms",
        "quote_title": "update_quote_title",
        "quote_name": "update_quote_title",
        "title": "update_quote_title",
        "expiration": "update_expiration_date",
        "expiry": "update_expiration_date",
        "expiration_date": "update_expiration_date",
        "expiry_date": "update_expiration_date",
        "purchase_order_number": "update_purchase_order_number",
        "purchase_order": "update_purchase_order_number",
        "po_number": "update_purchase_order_number",
        "po": "update_purchase_order_number",
        "add_sku": "add_product",
        "remove_sku": "remove_product",
        "quantity": "update_quantity",
    }
    candidate = aliases.get(normalized, normalized)
    if candidate not in {
        "update_discount_pct",
        "update_term_months",
        "update_payment_terms",
        "update_quote_title",
        "update_expiration_date",
        "update_purchase_order_number",
        "add_product",
        "remove_product",
        "update_quantity",
    }:
        raise RuntimeError(f"Unsupported quote intent operation: {value}")
    return candidate  # type: ignore[return-value]


def _quote_intent_is_line_item(intent: QuoteIntentV1) -> bool:
    return str(intent.semantic_key or "").strip() == "line_item"


def _parse_quote_intent_line_item_payload(raw_value: str) -> dict[str, Any]:
    raw_text = str(raw_value or "").strip()
    if not raw_text:
        return {}
    try:
        payload = json.loads(raw_text)
    except Exception:
        return {"product_reference": raw_text}
    return payload if isinstance(payload, dict) else {"product_reference": raw_text}


def _normalize_quote_line_reference_type(
    value: Any,
    product_reference: str,
) -> QuoteLineReferenceType:
    normalized = str(value or "").strip().lower()
    if normalized in {"product_id", "product_code", "product_name", "line_item_id"}:
        return normalized  # type: ignore[return-value]
    if _SELLER_PRODUCT_ID_RE.match(product_reference):
        return "product_id"
    if " " in product_reference.strip():
        return "product_name"
    return "product_code"


def _parse_quote_intent_line_quantity(value: Any) -> float:
    raw = str(value or "").strip().lower()
    if not raw:
        raise RuntimeError("Line-item preview requires a concrete quantity.")
    match = re.search(r"(\d+(?:\.\d+)?)", raw.replace(",", ""))
    if match is None:
        raise RuntimeError("Line-item preview requires a numeric quantity such as `5`.")
    quantity = float(match.group(1))
    if quantity <= 0:
        raise RuntimeError("Line-item preview requires a quantity greater than zero.")
    return round(quantity, 4)


def _quote_line_intent_target_label(operation: QuoteIntentOperation) -> str:
    if operation == "add_product":
        return "Add Product"
    if operation == "remove_product":
        return "Remove Product"
    if operation == "update_quantity":
        return "Update Quantity"
    raise RuntimeError(f"Unsupported line-item intent operation: {operation}")


def _quote_line_intent_display_value(
    *,
    operation: QuoteIntentOperation,
    product_reference: str,
    quantity: float | None,
) -> str:
    product_display = _quote_line_reference_display(product_reference)
    if operation == "add_product":
        return f"{product_display} x {_format_quantity(quantity)}"
    if operation == "remove_product":
        return product_display
    if operation == "update_quantity":
        return f"{product_display} -> {_format_quantity(quantity)}"
    raise RuntimeError(f"Unsupported line-item intent operation: {operation}")


def _parse_quote_intent_discount_pct(value: str) -> float:
    raw = str(value or "").strip()
    if not raw:
        raise RuntimeError("Discount preview requires a value such as `10%`.")
    has_percent = "%" in raw
    normalized = raw.replace("%", "").replace(",", "").strip()
    try:
        parsed = float(normalized)
    except Exception as exc:
        raise RuntimeError("Discount preview requires a numeric value such as `10%`.") from exc
    if parsed <= 0.0 or parsed > 100.0:
        raise RuntimeError("Discount preview requires a percentage between 0 and 100.")
    if not has_percent and parsed <= 1.0:
        raise RuntimeError("Use an explicit percent value like `10%` for discount preview.")
    return round(parsed, 4)


def _parse_quote_intent_term_months(value: str) -> int:
    raw = str(value or "").strip().lower()
    if not raw:
        raise RuntimeError("Term preview requires a value such as `24 months`.")
    match = re.search(r"(\d+(?:\.\d+)?)", raw)
    if match is None:
        raise RuntimeError("Term preview requires a numeric value such as `24 months`.")
    quantity = float(match.group(1))
    if quantity <= 0:
        raise RuntimeError("Term preview requires a positive term length.")
    if "year" in raw:
        months = int(round(quantity * 12))
    elif "month" in raw or "mo" in raw or raw == match.group(1):
        months = int(round(quantity))
    else:
        raise RuntimeError("Term preview currently supports month or year inputs only.")
    if months <= 0:
        raise RuntimeError("Term preview requires a positive month count.")
    return months


def _normalize_quote_intent_payment_terms(value: str) -> str:
    normalized = _strip_balanced_quotes(str(value or "").strip())
    if not normalized:
        raise RuntimeError("Payment-terms preview requires a concrete text value such as `Net 45`.")
    return normalized


def _normalize_quote_intent_quote_title(value: str) -> str:
    normalized = re.sub(r"\s+", " ", _strip_balanced_quotes(str(value or "").strip())).strip()
    if not normalized:
        raise RuntimeError("Quote-title preview requires a concrete title such as `Spring Renewal v2`.")
    return normalized


def _parse_quote_intent_expiration_date(value: str) -> str:
    raw = _strip_balanced_quotes(str(value or "").strip())
    if not raw:
        raise RuntimeError("Quote expiration preview requires a concrete date such as `2026-04-30`.")
    normalized = _normalize_iso_date_value(raw)
    if normalized:
        return normalized
    for fmt in ("%m/%d/%Y", "%m/%d/%y", "%m-%d-%Y", "%m-%d-%y"):
        try:
            return datetime.strptime(raw, fmt).date().isoformat()
        except Exception:
            continue
    raise RuntimeError(
        "Quote expiration preview requires a date like `2026-04-30` or `04/30/2026`."
    )


def _normalize_quote_intent_purchase_order_number(value: str) -> str:
    normalized = _strip_balanced_quotes(str(value or "").strip())
    if not normalized:
        raise RuntimeError("PO number preview requires a concrete value such as `PO-12345`.")
    return normalized


def _quote_request_from_intent(intent: QuoteIntentV1) -> dict[str, Any]:
    if intent.semantic_key == "discount_pct" and intent.requested_discount_pct is not None:
        return {"discount_pct": intent.requested_discount_pct}
    if intent.semantic_key == "term_months" and intent.requested_term_months is not None:
        return {"term_months": intent.requested_term_months}
    if intent.semantic_key == "payment_terms" and intent.requested_payment_terms is not None:
        return {"payment_terms": intent.requested_payment_terms}
    if intent.semantic_key == "quote_title" and intent.requested_quote_title is not None:
        return {"quote_title": intent.requested_quote_title}
    if intent.semantic_key == "expiration_date" and intent.requested_expiration_date is not None:
        return {"expiration_date": intent.requested_expiration_date}
    if intent.semantic_key == "purchase_order_number" and intent.requested_purchase_order_number is not None:
        return {"purchase_order_number": intent.requested_purchase_order_number}
    if intent.semantic_key == "line_item" and intent.requested_product_reference:
        line_item: dict[str, Any] = {
            str(intent.requested_product_reference_type or "product_code"): intent.requested_product_reference,
        }
        if intent.requested_quantity is not None:
            line_item["quantity"] = intent.requested_quantity
        return {"line_items": [line_item]}
    return {}


def _quote_line_reference_label(reference_type: QuoteLineReferenceType) -> str:
    if reference_type == "product_id":
        return "product id"
    if reference_type == "line_item_id":
        return "line item id"
    if reference_type == "product_name":
        return "product name"
    return "product code"


def _quote_line_reference_display(value: str | None) -> str:
    return str(value or "").strip() or "Unknown product"


def _normalize_quote_product_reference(value: str | None) -> str:
    return re.sub(r"\s+", " ", _strip_balanced_quotes(str(value or "").strip())).strip().lower()


def _format_quantity(value: float | None) -> str:
    if value is None:
        return "Not available"
    if abs(value - round(value)) < 0.0001:
        return str(int(round(value)))
    return f"{value:.4f}".rstrip("0").rstrip(".")


def _normalize_iso_date_value(value: Any) -> str | None:
    parsed = _parse_datetime_value(value)
    if parsed is None:
        return None
    return parsed.date().isoformat()


def _parse_datetime_value(value: Any) -> datetime | None:
    if value in (None, ""):
        return None
    if isinstance(value, datetime):
        return value if value.tzinfo is not None else value.replace(tzinfo=timezone.utc)
    if isinstance(value, date):
        return datetime.combine(value, datetime.min.time(), tzinfo=timezone.utc)
    raw = str(value).strip()
    if not raw:
        return None
    if len(raw) == 10:
        try:
            return datetime.combine(date.fromisoformat(raw), datetime.min.time(), tzinfo=timezone.utc)
        except Exception:
            pass
    normalized = raw[:-1] + "+00:00" if raw.endswith("Z") else raw
    try:
        parsed = datetime.fromisoformat(normalized)
    except Exception:
        return None
    return parsed if parsed.tzinfo is not None else parsed.replace(tzinfo=timezone.utc)


def _strip_balanced_quotes(value: str) -> str:
    text = str(value or "").strip()
    if len(text) >= 2 and text[0] == text[-1] and text[0] in {'"', "'"}:
        return text[1:-1].strip()
    return text
