"""
cortex/core/reconciliation/models.py — Data classes and enums for reconciliation.

Extracted from the monolithic reconciliation.py (#946).
"""

from __future__ import annotations

from dataclasses import dataclass, field
from datetime import datetime, timezone
from enum import Enum
from typing import List, Optional


# ---------------------------------------------------------------------------
# Shared helper — canonical UTC timestamp (single source of truth)
# ---------------------------------------------------------------------------

def _now() -> str:
    """ISO-8601 UTC timestamp with seconds precision."""
    return datetime.now(timezone.utc).isoformat(timespec="seconds").replace("+00:00", "Z")


# ---------------------------------------------------------------------------
# Enums & result types
# ---------------------------------------------------------------------------


class ReconciliationAction(str, Enum):
    """Action the reconciliation engine takes for an incoming claim."""

    ADD = "ADD"
    UPDATE = "UPDATE"
    SUPERSEDE = "SUPERSEDE"
    DELETE = "DELETE"
    NOOP = "NOOP"


class RelationshipKind(str, Enum):
    """Relationship between a new claim and an existing one."""
    # --- Existing (kept for backward compatibility) ---
    EQUIVALENT = "EQUIVALENT"
    COMPLEMENTARY = "COMPLEMENTARY"
    CONTRADICTION = "CONTRADICTION"
    NEW = "NEW"
    # --- M4 enriched relationship types (Issue #82) ---
    ELABORATES = "ELABORATES"          # new claim adds detail to existing → UPDATE
    CONTRADICTS = "CONTRADICTS"        # new claim directly conflicts → SUPERSEDE
    TEMPORAL_UPDATE = "TEMPORAL_UPDATE"  # time-based change ("moved from X to Y") → SUPERSEDE
    CONDITIONAL = "CONDITIONAL"        # context-dependent claim ("likes X when Y") → ADD


@dataclass
class ReconciliationResult:
    """Result of reconciling a single candidate claim."""
    candidate_subject: str
    candidate_predicate: str
    candidate_value: str
    action: ReconciliationAction = ReconciliationAction.NOOP
    # Set when an existing claim was found
    existing_claim_id: Optional[str] = None
    # Set on ADD / UPDATE — the new/updated claim's ID
    resulting_claim_id: Optional[str] = None
    # Relationship classification that drove the decision
    relationship: Optional[RelationshipKind] = None
    confidence: float = 0.0
    # True if update was blocked by anti-compression safeguard
    compression_blocked: bool = False
    # True if an LLM call was consumed for this result
    used_llm: bool = False
    notes: str = ""
    # Structured metadata from the relationship classification (M4 / Issue #82).
    # Populated for richer relationship kinds:
    #   ELABORATES     → {"detail_added": "<new aspect>"}
    #   TEMPORAL_UPDATE→ {"previous_value": "<old>", "new_value": "<new>"}
    #   CONDITIONAL    → {"condition": "<context string>"}
    #   CONTRADICTS    → {"conflict_reason": "<brief reason>"}
    relationship_metadata: Optional[dict] = None


@dataclass
class ReconciliationReport:
    """Aggregate stats for a full reconcile() call."""
    added: int = 0
    updated: int = 0
    superseded: int = 0
    deleted: int = 0
    nooped: int = 0
    compression_blocked: int = 0
    cornerstone_guarded: int = 0
    entities_resolved: int = 0
    entities_created: int = 0
    edges_created: int = 0
    duration_ms: int = 0
    llm_calls_used: int = 0
    flagged_for_review: int = 0
    results: List[ReconciliationResult] = field(default_factory=list)


# ---------------------------------------------------------------------------
# LLM prompt template
# ---------------------------------------------------------------------------

RECONCILIATION_PROMPT = """Compare these two memory claims and classify their relationship.

EXISTING claim:
Subject: {existing_subject}
Predicate: {existing_predicate}
Value: {existing_value}

NEW candidate:
Subject: {candidate_subject}
Predicate: {candidate_predicate}
Value: {candidate_value}

Classify as ONE of:
- EQUIVALENT: Identical information — no update needed.
- COMPLEMENTARY: New info extends or enriches existing in a general way (merge/update).
- ELABORATES: New claim adds specific detail or nuance to existing (e.g. "likes coffee"
  → "likes strong black coffee in the morning"). Prefer over COMPLEMENTARY when the new
  value deepens a specific aspect rather than adding breadth.
- CONTRADICTS: New claim directly conflicts with or negates existing info (supersede old).
  Use when the two values cannot both be true at the same time.
- TEMPORAL_UPDATE: The change is explicitly time-based — the subject moved, changed role,
  or updated a preference over time (e.g. "lives in Tokyo" → "moved to Bangkok"). The old
  value was once true but is now outdated.
- CONDITIONAL: The new claim is explicitly context-dependent and should be stored
  separately from any unconditional version (e.g. "likes jazz when working late").
  It is NOT a contradiction — both can be true.
- NEW: Completely different topic — add as a separate claim.

Also assess: would merging lose important personality, emotional, or contextual detail?
If so, prefer ELABORATES or NEW over COMPLEMENTARY.

Output JSON only:
{{"classification": "EQUIVALENT|COMPLEMENTARY|ELABORATES|CONTRADICTS|TEMPORAL_UPDATE|CONDITIONAL|NEW",
  "confidence": 0.0-1.0,
  "reasoning": "brief explanation",
  "metadata": {{
    "detail_added": "(ELABORATES only) the new specific aspect added",
    "previous_value": "(TEMPORAL_UPDATE only) the old value being replaced",
    "new_value": "(TEMPORAL_UPDATE only) the new current value",
    "condition": "(CONDITIONAL only) the context/condition string",
    "conflict_reason": "(CONTRADICTS only) why the two values conflict"
  }}
}}"""
