"""Fact upsert — execute reconciliation decisions.

Ported from advisor_api memory_upsert.py (V2 section only).
Creates/versions MemoryFact records and generates embeddings via EmbeddingProvider.
"""

import logging
from dataclasses import dataclass
from datetime import UTC, datetime
from typing import Any

from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from arandu.models import MemoryEntityRelationship, MemoryFact
from arandu.protocols import EmbeddingProvider
from arandu.write.entity_resolution import ResolvedEntity
from arandu.write.reconcile import ReconciliationDecision

logger = logging.getLogger(__name__)


# ---------------------------------------------------------------------------
# Result types
# ---------------------------------------------------------------------------
@dataclass
class UpsertResult:
    """Result of executing reconciliation decisions."""

    added: int = 0
    updated: int = 0
    confirmed: int = 0
    deleted: int = 0


@dataclass
class RelationUpsertResult:
    """Result of upserting extracted relations."""

    created: int = 0


# ---------------------------------------------------------------------------
# Decision executors
# ---------------------------------------------------------------------------
async def _add_fact(
    session: AsyncSession,
    user_id: str,
    source_event_id: str | None,
    decision: ReconciliationDecision,
    entity: ResolvedEntity,
    now: datetime,
    embeddings: EmbeddingProvider,
) -> None:
    """Create a new MemoryFact from reconciliation decision."""
    fact_text = f"{entity.display_name}: {decision.fact_text}"
    embedding = await embeddings.embed_one(fact_text)

    new_fact = MemoryFact(
        user_id=user_id,
        entity_type=entity.entity_type,
        entity_key=entity.entity_key,
        entity_name=entity.display_name,
        attribute_key=None,
        value_json=None,
        value_text=None,
        fact_text=decision.fact_text,
        confidence=decision.confidence,
        importance=0.5,
        valid_from=now,
        valid_to=None,
        source_event_id=source_event_id,
        last_confirmed_at=now,
        ingested_at=now,
        embedding_vec=embedding,
    )
    session.add(new_fact)
    await session.flush()


async def _update_fact(
    session: AsyncSession,
    user_id: str,
    source_event_id: str | None,
    decision: ReconciliationDecision,
    entity: ResolvedEntity,
    now: datetime,
    embeddings: EmbeddingProvider,
) -> None:
    """Close old fact and create new one (UPDATE action)."""
    if decision.matched_fact_id:
        stmt = select(MemoryFact).where(MemoryFact.id == decision.matched_fact_id)
        result = await session.execute(stmt)
        old_fact = result.scalar_one_or_none()
        if old_fact:
            old_fact.valid_to = now
            old_fact.invalidated_at = now
            session.add(old_fact)

    fact_text = f"{entity.display_name}: {decision.fact_text}"
    embedding = await embeddings.embed_one(fact_text)

    new_fact = MemoryFact(
        user_id=user_id,
        entity_type=entity.entity_type,
        entity_key=entity.entity_key,
        entity_name=entity.display_name,
        attribute_key=None,
        value_json=None,
        value_text=None,
        fact_text=decision.fact_text,
        confidence=decision.confidence,
        importance=0.5,
        valid_from=now,
        valid_to=None,
        source_event_id=source_event_id,
        supersedes_fact_id=decision.matched_fact_id,
        last_confirmed_at=now,
        ingested_at=now,
        embedding_vec=embedding,
    )
    session.add(new_fact)
    await session.flush()


async def _confirm_fact(
    session: AsyncSession,
    decision: ReconciliationDecision,
    now: datetime,
) -> None:
    """Update last_confirmed_at on an existing fact (NOOP action)."""
    if decision.matched_fact_id:
        stmt = select(MemoryFact).where(MemoryFact.id == decision.matched_fact_id)
        result = await session.execute(stmt)
        fact = result.scalar_one_or_none()
        if fact:
            fact.last_confirmed_at = now
            session.add(fact)


async def _delete_fact(
    session: AsyncSession,
    decision: ReconciliationDecision,
    now: datetime,
) -> None:
    """Close a fact by setting valid_to (DELETE action)."""
    if decision.matched_fact_id:
        stmt = select(MemoryFact).where(MemoryFact.id == decision.matched_fact_id)
        result = await session.execute(stmt)
        fact = result.scalar_one_or_none()
        if fact:
            fact.valid_to = now
            fact.invalidated_at = now
            session.add(fact)


# ---------------------------------------------------------------------------
# Main entry point
# ---------------------------------------------------------------------------
async def execute_upsert(
    session: AsyncSession,
    user_id: str,
    source_event_id: str | None,
    decisions: list[ReconciliationDecision],
    entity_map: dict[str, ResolvedEntity],
    embeddings: EmbeddingProvider,
) -> UpsertResult:
    """Execute reconciliation decisions: ADD, UPDATE, NOOP, DELETE.

    Uses savepoint pattern for each decision to protect the outer transaction.

    Args:
        session: Database session.
        user_id: User identifier.
        source_event_id: ID of the source MemoryEvent.
        decisions: Reconciliation decisions to execute.
        entity_map: Resolved entities mapping.
        embeddings: Injected embedding provider.

    Returns:
        UpsertResult with counts.
    """
    result = UpsertResult()
    now = datetime.now(UTC)

    for decision in decisions:
        subj_key = decision.subject.lower().strip()
        entity = entity_map.get(subj_key)

        if entity is None:
            logger.warning(
                "execute_upsert: no entity for subject '%s', skipping",
                decision.subject,
            )
            continue

        try:
            if decision.action == "ADD":
                async with session.begin_nested():
                    await _add_fact(
                        session,
                        user_id,
                        source_event_id,
                        decision,
                        entity,
                        now,
                        embeddings,
                    )
                result.added += 1

            elif decision.action == "UPDATE":
                async with session.begin_nested():
                    await _update_fact(
                        session,
                        user_id,
                        source_event_id,
                        decision,
                        entity,
                        now,
                        embeddings,
                    )
                result.updated += 1

            elif decision.action == "NOOP":
                await _confirm_fact(session, decision, now)
                result.confirmed += 1

            elif decision.action == "DELETE":
                await _delete_fact(session, decision, now)
                result.deleted += 1
        except Exception as e:
            logger.warning(
                "execute_upsert: failed to execute %s for '%s': %s",
                decision.action,
                decision.subject,
                e,
            )

    logger.info(
        "execute_upsert: user=%s, added=%d, updated=%d, confirmed=%d, deleted=%d",
        user_id,
        result.added,
        result.updated,
        result.confirmed,
        result.deleted,
    )
    return result


# ---------------------------------------------------------------------------
# Entity alias registration
# ---------------------------------------------------------------------------
async def register_entity_alias(
    session: AsyncSession,
    user_id: str,
    canonical_key: str,
    alias: str,
    entity_type: str = "other",
) -> None:
    """Register an alias for an entity key using the MemoryEntityAlias table.

    Uses ON CONFLICT DO NOTHING for first-write-wins semantics.

    Args:
        session: Database session.
        user_id: User identifier.
        canonical_key: The canonical entity key.
        alias: The alias to register.
        entity_type: Entity type for the alias record.
    """
    from sqlalchemy.dialects.postgresql import insert as pg_insert

    from arandu.models import MemoryEntityAlias

    alias_normalized = alias.lower().strip()

    try:
        stmt = (
            pg_insert(MemoryEntityAlias)
            .values(
                user_id=user_id,
                alias=alias_normalized,
                canonical_entity_key=canonical_key,
                canonical_entity_type=entity_type,
            )
            .on_conflict_do_nothing()
        )
        await session.execute(stmt)
        logger.debug("register_entity_alias: key=%s, alias=%s", canonical_key, alias)
    except Exception as e:
        logger.debug("register_entity_alias: failed for key=%s alias=%s: %s", canonical_key, alias, e)


# ---------------------------------------------------------------------------
# Relation tracking
# ---------------------------------------------------------------------------
async def track_entity_relationship(
    session: AsyncSession,
    user_id: str,
    source_key: str,
    target_key: str,
    rel_type: str,
    *,
    strength: float = 0.5,
) -> None:
    """Create or reinforce a relationship between two entities.

    Args:
        session: Database session.
        user_id: User identifier.
        source_key: Source entity key.
        target_key: Target entity key.
        rel_type: Relationship type.
        strength: Initial/reinforcement strength.
    """
    stmt = select(MemoryEntityRelationship).where(
        MemoryEntityRelationship.user_id == user_id,
        MemoryEntityRelationship.source_entity_key == source_key,
        MemoryEntityRelationship.target_entity_key == target_key,
        MemoryEntityRelationship.rel_type == rel_type,
    )
    result = await session.execute(stmt)
    existing = result.scalar_one_or_none()

    if existing:
        existing.strength = min(1.0, (existing.strength or 0.5) + 0.05)
        existing.last_used_at = datetime.now(UTC)
        session.add(existing)
    else:
        rel = MemoryEntityRelationship(
            user_id=user_id,
            source_entity_key=source_key,
            target_entity_key=target_key,
            rel_type=rel_type,
            strength=strength,
            last_used_at=datetime.now(UTC),
        )
        session.add(rel)
        await session.flush()

    logger.debug(
        "track_entity_relationship: %s -> %s (%s)",
        source_key,
        target_key,
        rel_type,
    )


async def upsert_relations(
    session: AsyncSession,
    user_id: str,
    source_event_id: str | None,
    relations: Any,
    entity_map: dict[str, ResolvedEntity],
) -> RelationUpsertResult:
    """Upsert relationships from extraction.

    Resolves source/target via entity_map. Uses ON CONFLICT to deduplicate.
    """
    from sqlalchemy import text
    from sqlalchemy.dialects.postgresql import insert as pg_insert

    result = RelationUpsertResult()

    for rel in relations:
        source_key_lookup = rel.source.lower().strip()
        target_key_lookup = rel.target.lower().strip()

        source_entity = entity_map.get(source_key_lookup)
        target_entity = entity_map.get(target_key_lookup)

        if not source_entity or not target_entity:
            logger.debug(
                "upsert_relations: skipping rel %s->%s (unresolved entity)",
                rel.source,
                rel.target,
            )
            continue

        try:
            async with session.begin_nested():
                stmt = (
                    pg_insert(MemoryEntityRelationship)
                    .values(
                        user_id=user_id,
                        source_entity_key=source_entity.entity_key,
                        target_entity_key=target_entity.entity_key,
                        rel_type=rel.rel_type,
                        strength=0.8,
                        provenance="llm",
                        valid_from=datetime.now(UTC),
                    )
                    .on_conflict_do_update(
                        constraint="uq_entity_relationship",
                        set_={
                            "strength": text("GREATEST(memory_entity_relationships.strength, EXCLUDED.strength)"),
                            "updated_at": datetime.now(UTC),
                        },
                    )
                )
                await session.execute(stmt)
            result.created += 1
        except Exception as e:
            logger.warning(
                "upsert_relations: failed %s->%s (%s): %s",
                rel.source,
                rel.target,
                rel.rel_type,
                e,
            )

    if relations:
        logger.info(
            "upsert_relations: user=%s, processed=%d, created=%d",
            user_id,
            len(relations),
            result.created,
        )
    return result
