"""Canonical merger: combine rule parser output with LLM output."""
from __future__ import annotations

from copy import deepcopy
from typing import Any

_HIGH_CONFIDENCE = 0.70
_UNKNOWN_VALUES = {"unknown", "unclear", "not_provided"}


def _is_confident(output: dict[str, Any], field: str | None = None) -> bool:
    """Return True if output has overall confidence >= threshold."""
    conf = output.get("confidence", {})
    if field and field in conf:
        return float(conf[field]) >= _HIGH_CONFIDENCE
    return float(conf.get("overall", 0.0)) >= _HIGH_CONFIDENCE


def _field_is_unknown(value: Any) -> bool:
    return str(value).lower() in _UNKNOWN_VALUES if value is not None else True


def _merge_scalar(
    rule_val: Any,
    llm_val: Any | None,
    rule_conf: float,
    llm_conf: float,
    field_name: str,
    warnings: list[str],
    provenance: dict[str, str],
) -> Any:
    """Merge a single scalar field with provenance tracking.

    Strategy:
    - Both confident + disagree → conflict flag, rule wins (conservative).
    - Only rule confident → rule wins.
    - Only LLM confident → LLM wins (fills in unknown/low-confidence rule output).
    - Neither confident → prefer non-unknown value, rule takes priority.
    """
    rule_unknown = _field_is_unknown(rule_val)
    llm_unknown = llm_val is None or _field_is_unknown(llm_val)
    rule_confident = not rule_unknown and rule_conf >= _HIGH_CONFIDENCE
    llm_confident = not llm_unknown and llm_conf >= _HIGH_CONFIDENCE

    # Both confident and disagree → record conflict; rule wins but conflict is flagged
    if rule_confident and llm_confident and str(rule_val) != str(llm_val):
        warnings.append(f"rule_llm_conflict:{field_name} rule={rule_val} llm={llm_val}")
        provenance[field_name] = "rule"
        return rule_val

    # Rule wins when confident (and no conflict detected above)
    if rule_confident:
        provenance[field_name] = "rule"
        return rule_val

    # LLM wins when confident (rule is not confident)
    if llm_confident:
        provenance[field_name] = "llm"
        return llm_val

    # Neither confident — prefer non-unknown, rule first
    if not rule_unknown:
        provenance[field_name] = "rule"
        return rule_val

    if not llm_unknown:
        provenance[field_name] = "llm"
        return llm_val

    provenance[field_name] = "rule"
    return rule_val  # both unknown → keep rule default


def merge(
    rule_output: dict[str, Any],
    llm_output: dict[str, Any] | None,
) -> dict[str, Any]:
    """Merge rule parser output with optional LLM output into a canonical record.

    Strategy:
    - Rule parser wins when confident (overall >= 0.70).
    - LLM fills in unknown fields when it is confident.
    - Conflicts → conservative merge ("unknown"/"mixed") + warning.
    - Adds ``provenance`` dict and ``rule_llm_conflict`` flag.
    """
    result = deepcopy(rule_output)
    result["parser_kind"] = "merged"
    provenance: dict[str, str] = {}
    warnings: list[str] = list(result.get("warnings", []))
    has_conflict = False

    if llm_output is None:
        # No LLM — everything from rule
        result["provenance"] = dict.fromkeys(_CANONICAL_FIELDS, "rule")
        result["rule_llm_conflict"] = False
        return result

    rule_conf = float(rule_output.get("confidence", {}).get("overall", 0.0))
    llm_conf = float(llm_output.get("confidence", {}).get("overall", 0.0))

    # Merge top-level scalar fields
    for field in ("event_type", "event_direction"):
        merged_val = _merge_scalar(
            rule_val=rule_output.get(field),
            llm_val=llm_output.get(field),
            rule_conf=float(rule_output.get("confidence", {}).get(field, rule_conf)),
            llm_conf=float(llm_output.get("confidence", {}).get(field, llm_conf)),
            field_name=field,
            warnings=warnings,
            provenance=provenance,
        )
        result[field] = merged_val
        if any(f"rule_llm_conflict:{field}" in w for w in warnings):
            has_conflict = True

    # Headline / summary: prefer LLM (usually more informative)
    if llm_output.get("headline") and not _field_is_unknown(llm_output.get("headline")):
        result["headline"] = llm_output["headline"]
        provenance["headline"] = "llm"
    else:
        provenance["headline"] = "rule"

    if llm_output.get("summary") and not _field_is_unknown(llm_output.get("summary")):
        result["summary"] = llm_output["summary"]
        provenance["summary"] = "llm"
    else:
        provenance["summary"] = "rule"

    # Guidance
    rule_guidance = rule_output.get("guidance", {})
    llm_guidance = llm_output.get("guidance", {})
    merged_guidance = deepcopy(rule_guidance)
    guidance_rule_conf = float(rule_output.get("confidence", {}).get("guidance", rule_conf))
    guidance_llm_conf = float(llm_output.get("confidence", {}).get("guidance", llm_conf))

    g_status = _merge_scalar(
        rule_val=rule_guidance.get("status"),
        llm_val=llm_guidance.get("status"),
        rule_conf=guidance_rule_conf,
        llm_conf=guidance_llm_conf,
        field_name="guidance.status",
        warnings=warnings,
        provenance=provenance,
    )
    merged_guidance["status"] = g_status
    if any("rule_llm_conflict:guidance.status" in w for w in warnings):
        has_conflict = True

    if llm_guidance.get("scope") and not _field_is_unknown(llm_guidance.get("scope")):
        if _field_is_unknown(rule_guidance.get("scope")):
            merged_guidance["scope"] = llm_guidance["scope"]
            provenance["guidance.scope"] = "llm"
        else:
            provenance["guidance.scope"] = "rule"
    else:
        provenance["guidance.scope"] = "rule"

    result["guidance"] = merged_guidance

    # Signals: LLM fills in unknown rule signals
    rule_signals = rule_output.get("signals", {})
    llm_signals = llm_output.get("signals", {})
    merged_signals = deepcopy(rule_signals)
    for sig_field in (
        "demand_strength",
        "pricing_power",
        "backlog_or_bookings",
        "customer_expansion",
        "margin_quality",
    ):
        if _field_is_unknown(rule_signals.get(sig_field)) and not _field_is_unknown(
            llm_signals.get(sig_field)
        ):
            merged_signals[sig_field] = llm_signals[sig_field]
            provenance[f"signals.{sig_field}"] = "llm"
        else:
            provenance[f"signals.{sig_field}"] = "rule"
    result["signals"] = merged_signals

    # Risk flags: OR of rule + LLM (either flagging = flagged)
    rule_risks = rule_output.get("risk_flags", {})
    llm_risks = llm_output.get("risk_flags", {})
    merged_risks: dict[str, bool] = {}
    for risk_field in (
        "oneoff_item",
        "tax_benefit",
        "valuation_gain",
        "non_gaap_heavy",
        "financing_related",
        "legal_or_regulatory_overhang",
    ):
        rule_flag = bool(rule_risks.get(risk_field, False))
        llm_flag = bool(llm_risks.get(risk_field, False))
        merged_risks[risk_field] = rule_flag or llm_flag
        if rule_flag and llm_flag:
            provenance[f"risk_flags.{risk_field}"] = "merged"
        elif rule_flag:
            provenance[f"risk_flags.{risk_field}"] = "rule"
        elif llm_flag:
            provenance[f"risk_flags.{risk_field}"] = "llm"
        else:
            provenance[f"risk_flags.{risk_field}"] = "rule"
    result["risk_flags"] = merged_risks

    # Confidence: use higher overall
    if llm_conf > rule_conf:
        result["confidence"] = llm_output.get("confidence", result.get("confidence", {}))
        provenance["confidence"] = "llm"
    else:
        provenance["confidence"] = "rule"

    result["provenance"] = provenance
    result["rule_llm_conflict"] = has_conflict
    result["warnings"] = warnings

    return result


# Canonical fields used for provenance initialization
_CANONICAL_FIELDS = [
    "event_type",
    "event_direction",
    "headline",
    "summary",
    "guidance.status",
    "guidance.scope",
    "confidence",
    *[f"signals.{f}" for f in ("demand_strength", "pricing_power", "backlog_or_bookings",
                                "customer_expansion", "margin_quality")],
    *[f"risk_flags.{f}" for f in ("oneoff_item", "tax_benefit", "valuation_gain",
                                   "non_gaap_heavy", "financing_related",
                                   "legal_or_regulatory_overhang")],
]


def should_queue_for_review(merged: dict[str, Any]) -> tuple[bool, list[str]]:
    """Determine if a merged record needs human review.

    Returns:
        (should_queue, reason_codes) where reason_codes is a list of strings.
    """
    reason_codes: list[str] = []

    conf = float(merged.get("confidence", {}).get("overall", 0.0))
    if conf < _HIGH_CONFIDENCE:
        reason_codes.append("low_confidence")

    if merged.get("rule_llm_conflict"):
        reason_codes.append("rule_llm_conflict")

    guidance = merged.get("guidance", {})
    if guidance.get("status") in ("unclear",):
        reason_codes.append("guidance_ambiguous")

    risk_flags = merged.get("risk_flags", {})
    if risk_flags.get("oneoff_item") or risk_flags.get("non_gaap_heavy"):
        reason_codes.append("oneoff_likely")

    event_direction = merged.get("event_direction", "unknown")
    if event_direction in ("unknown", "mixed"):
        reason_codes.append("direction_ambiguous")

    return bool(reason_codes), reason_codes
