"""Contradiction Detector — finds conflicting memories via similarity + negation heuristics."""

from __future__ import annotations

from dataclasses import dataclass, field
from datetime import datetime
from typing import Dict, List, Optional

import numpy as np

from .node import MemoryNode
from .store import MemoryStore, _cosine_similarity

# Words/phrases that suggest negation or reversal
_NEGATION_MARKERS = {
    "not", "never", "no longer", "don't", "doesn't", "didn't",
    "won't", "wouldn't", "shouldn't", "can't", "cannot", "isn't",
    "aren't", "wasn't", "weren't", "instead", "rather than",
    "switched from", "changed from", "stopped", "quit", "dropped",
    "replaced", "moved away from", "no more",
}

# Antonym pairs that suggest contradiction
_ANTONYM_PAIRS = [
    ("prefer", "avoid"), ("use", "stopped using"), ("like", "dislike"),
    ("enabled", "disabled"), ("true", "false"), ("yes", "no"),
    ("always", "never"), ("increase", "decrease"), ("accept", "reject"),
]


@dataclass
class ConflictWarning:
    """A detected contradiction between two memories."""
    node_a: MemoryNode
    node_b: MemoryNode
    similarity: float
    reason: str
    suggested_resolution: str = ""


def _has_negation_signal(text_a: str, text_b: str) -> Optional[str]:
    """Check if two texts contain negation markers suggesting contradiction."""
    a_lower = text_a.lower()
    b_lower = text_b.lower()

    for marker in _NEGATION_MARKERS:
        if marker in a_lower and marker not in b_lower:
            return f"Negation marker '{marker}' present in one but not the other"
        if marker in b_lower and marker not in a_lower:
            return f"Negation marker '{marker}' present in one but not the other"

    for word_a, word_b in _ANTONYM_PAIRS:
        if (word_a in a_lower and word_b in b_lower) or (word_b in a_lower and word_a in b_lower):
            return f"Antonym pair detected: '{word_a}' vs '{word_b}'"

    return None


def check_conflicts(
    store: MemoryStore,
    scope: Optional[str] = None,
    similarity_threshold: float = 0.85,
    limit: int = 500,
) -> List[ConflictWarning]:
    """Scan for contradictory memories.

    Two memories are flagged as conflicting when:
    1. Their embeddings have cosine similarity >= threshold (they discuss the same topic)
    2. They contain negation markers or antonym signals suggesting opposing claims

    Returns a list of ConflictWarning objects.
    """
    nodes = store.list_nodes(scope=scope, min_importance=0.0, limit=limit)
    # Filter to non-deleted nodes with embeddings
    nodes = [n for n in nodes if n.embedding and n.deleted_at is None]

    conflicts: List[ConflictWarning] = []
    seen_pairs: set = set()

    for i, node_a in enumerate(nodes):
        vec_a = np.asarray(node_a.embedding, dtype=np.float32)
        for j in range(i + 1, len(nodes)):
            node_b = nodes[j]
            pair_key = tuple(sorted([node_a.id, node_b.id]))
            if pair_key in seen_pairs:
                continue
            seen_pairs.add(pair_key)

            vec_b = np.asarray(node_b.embedding, dtype=np.float32)
            sim = _cosine_similarity(vec_a, vec_b)

            if sim < similarity_threshold:
                continue

            reason = _has_negation_signal(node_a.content, node_b.content)
            if reason is None:
                continue

            # Determine which is newer
            newer = node_a if node_a.timestamp >= node_b.timestamp else node_b
            older = node_b if newer is node_a else node_a
            suggestion = (
                f"The newer memory (from {newer.timestamp.strftime('%Y-%m-%d')}) "
                f"likely supersedes the older one (from {older.timestamp.strftime('%Y-%m-%d')}). "
                f"Consider using db.correct() to explicitly resolve."
            )

            conflicts.append(ConflictWarning(
                node_a=node_a,
                node_b=node_b,
                similarity=round(sim, 4),
                reason=reason,
                suggested_resolution=suggestion,
            ))

    return conflicts


def check_new_memory_conflicts(
    store: MemoryStore,
    new_node: MemoryNode,
    similarity_threshold: float = 0.85,
    scope: Optional[str] = None,
    limit: int = 200,
) -> List[ConflictWarning]:
    """Check a single new memory against existing memories for conflicts."""
    if not new_node.embedding:
        return []

    existing = store.list_nodes(scope=scope, min_importance=0.0, limit=limit)
    vec_new = np.asarray(new_node.embedding, dtype=np.float32)
    conflicts: List[ConflictWarning] = []

    for node in existing:
        if node.id == new_node.id or not node.embedding or node.deleted_at is not None:
            continue
        vec = np.asarray(node.embedding, dtype=np.float32)
        sim = _cosine_similarity(vec_new, vec)
        if sim < similarity_threshold:
            continue
        reason = _has_negation_signal(new_node.content, node.content)
        if reason:
            conflicts.append(ConflictWarning(
                node_a=new_node,
                node_b=node,
                similarity=round(sim, 4),
                reason=reason,
                suggested_resolution=f"New memory may supersede existing memory [{node.id[:8]}].",
            ))

    return conflicts
