"""
URD insert pipeline — collision detection and write-through.

Insert flow:
  key = (concept_id, dim_id)
  if key in urd_by_concept_dim → collision detected in-memory
      → classify (is_isa flags), route to resolution queue
  else
      → INSERT into Postgres URD
      → on success: update urd_by_concept + urd_by_concept_dim
      → on UniqueViolation (race): reload row, route to resolution queue
"""
from __future__ import annotations

import logging
from dataclasses import dataclass
from typing import Optional

import asyncpg

from . import cache
from .cache import UrdEdge

log = logging.getLogger("festinger.urd_writer")


@dataclass
class InsertRequest:
    concept_id: int
    parent_id: int
    dim_id: int
    is_isa: bool
    confidence: float
    source: str


@dataclass
class CollisionInfo:
    concept_id: int
    existing_parent_id: int
    incoming_parent_id: int
    dim_id: int
    existing_is_isa: bool
    incoming_is_isa: bool

    @property
    def collision_type(self) -> str:
        if self.existing_is_isa and self.incoming_is_isa:
            return "isa_isa"
        if not self.existing_is_isa and not self.incoming_is_isa:
            return "ispart_ispart"
        return "misclassification"


async def insert_urd_edge(
    pool: asyncpg.Pool,
    req: InsertRequest,
    priority: bool = False,
) -> Optional[CollisionInfo]:
    """
    Attempt to insert a URD edge.
    Returns CollisionInfo if there was a collision, None on success.
    """
    key = (req.concept_id, req.dim_id)

    # Fast-path collision detection — in-memory
    existing = cache.urd_by_concept_dim.get(key)
    if existing is not None:
        if existing.parent_id == req.parent_id:
            # Identical re-assertion of an already-stored fact — silent no-op.
            # This is normal: the cue scanner re-extracts the same facts from
            # conversation history on every round.
            log.debug(
                "urd skip identical  concept=%d parent=%d dim=%d",
                req.concept_id, req.parent_id, req.dim_id,
            )
            return None
        collision = CollisionInfo(
            concept_id=req.concept_id,
            existing_parent_id=existing.parent_id,
            incoming_parent_id=req.parent_id,
            dim_id=req.dim_id,
            existing_is_isa=existing.is_isa,
            incoming_is_isa=req.is_isa,
        )
        await _queue_collision(pool, collision, priority)
        return collision

    # Attempt Postgres insert
    try:
        async with pool.acquire() as conn:
            await conn.execute(
                """
                INSERT INTO urd (id, parent_id, dim_id, is_isa, confidence, source)
                VALUES ($1, $2, $3, $4, $5, $6)
                """,
                req.concept_id, req.parent_id, req.dim_id,
                req.is_isa, req.confidence, req.source,
            )
    except asyncpg.UniqueViolationError:
        # Race condition — another process inserted concurrently
        async with pool.acquire() as conn:
            row = await conn.fetchrow(
                "SELECT parent_id, is_isa FROM urd WHERE id=$1 AND dim_id=$2",
                req.concept_id, req.dim_id,
            )
        if row:
            collision = CollisionInfo(
                concept_id=req.concept_id,
                existing_parent_id=row["parent_id"],
                incoming_parent_id=req.parent_id,
                dim_id=req.dim_id,
                existing_is_isa=row["is_isa"],
                incoming_is_isa=req.is_isa,
            )
            await _queue_collision(pool, collision, priority)
            return collision
        return None

    # Success — update in-memory cache
    parent_token = cache.soas_by_id.get(req.parent_id, str(req.parent_id))
    dim_token = cache.soas_by_id.get(req.dim_id, str(req.dim_id))
    edge = UrdEdge(
        concept_id=req.concept_id,
        parent_id=req.parent_id,
        dim_id=req.dim_id,
        is_isa=req.is_isa,
        confidence=req.confidence,
        source=req.source,
        parent_token=parent_token,
        dim_token=dim_token,
    )
    cache.urd_by_concept.setdefault(req.concept_id, []).append(edge)
    cache.urd_by_concept_dim[key] = edge
    cache.urd_by_parent.setdefault(req.parent_id, []).append(edge)

    # Mark the concept as a domain token: saliency=1 means "first use".
    # saliency=0 is the sentinel for common-English words (dictionary seed),
    # which the encounter loop silently skips.  Any concept that has a URD
    # edge is definitionally domain-specific and must start above zero.
    concept_token_str = cache.soas_by_id.get(req.concept_id, "")
    row = cache.soas_by_token.get(concept_token_str)
    if row and row.saliency == 0.0:
        async with pool.acquire() as conn:
            await conn.execute(
                "UPDATE soas SET saliency = 1.0, novelty = 1.0 WHERE id = $1 AND saliency = 0",
                req.concept_id,
            )
        row.saliency = 1.0
        row.novelty = 1.0
    log.info(
        "urd insert  concept=%d parent=%d dim=%d is_isa=%s source=%s",
        req.concept_id, req.parent_id, req.dim_id, req.is_isa, req.source,
    )
    await log_kg_write(
        pool, "insert", req,
        concept_token=cache.soas_by_id.get(req.concept_id, str(req.concept_id)),
        parent_token=parent_token,
        dim_token=dim_token,
    )
    return None


async def log_kg_write(
    pool: asyncpg.Pool,
    op: str,
    req: InsertRequest,
    *,
    concept_token: str,
    parent_token: str,
    dim_token: str,
    prev_parent_id: Optional[int] = None,
    prev_parent_token: Optional[str] = None,
    resolution_queue_id: Optional[int] = None,
) -> None:
    """Append one row to kg_write_log. Fire-and-forget — never raises."""
    try:
        async with pool.acquire() as conn:
            await conn.execute(
                """
                INSERT INTO kg_write_log
                  (op, concept_id, concept_token, parent_id, parent_token,
                   prev_parent_id, prev_parent_token, dim_id, dim_token,
                   is_isa, confidence, source, resolution_queue_id)
                VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13)
                """,
                op,
                req.concept_id, concept_token,
                req.parent_id, parent_token,
                prev_parent_id, prev_parent_token,
                req.dim_id, dim_token,
                req.is_isa, req.confidence, req.source,
                resolution_queue_id,
            )
    except Exception:
        log.exception("kg_write_log insert failed (op=%s concept=%d)", op, req.concept_id)


async def _queue_collision(
    pool: asyncpg.Pool,
    col: CollisionInfo,
    priority: bool,
) -> None:
    cache.pending_conflicts.add(col.concept_id)
    async with pool.acquire() as conn:
        await conn.execute(
            """
            INSERT INTO resolution_queue
              (concept_id, existing_parent_id, incoming_parent_id, dim_id,
               collision_type, priority)
            VALUES ($1, $2, $3, $4, $5, $6)
            """,
            col.concept_id,
            col.existing_parent_id,
            col.incoming_parent_id,
            col.dim_id,
            col.collision_type,
            priority,
        )
    log.warning(
        "collision queued  concept=%d type=%s priority=%s",
        col.concept_id, col.collision_type, priority,
    )
