"""Write pipeline orchestrator — extract → resolve → reconcile → upsert.

Ported from advisor_api memory_service.py. Simplified: no trace, no runtime config.
All dependencies injected via function parameters.
"""

import logging
import time
from datetime import UTC, datetime

from sqlalchemy.ext.asyncio import AsyncSession

from arandu.config import MemoryConfig
from arandu.constants import ExtractionOutput
from arandu.models import MemoryEvent
from arandu.protocols import EmbeddingProvider, LLMProvider
from arandu.write.entity_resolution import resolve_entities
from arandu.write.extract import extract_from_message
from arandu.write.reconcile import reconcile_facts
from arandu.write.upsert import UpsertResult, execute_upsert, upsert_relations

logger = logging.getLogger(__name__)


async def _create_event(
    session: AsyncSession,
    user_id: str,
    text: str,
    source: str,
    embeddings: EmbeddingProvider,
) -> MemoryEvent:
    """Create an immutable event record for a user message."""
    embedding = await embeddings.embed_one(text)

    event = MemoryEvent(
        user_id=user_id,
        occurred_at=datetime.now(UTC),
        text=text,
        source=source,
        importance=0.5,
        embedding_vec=embedding,
    )
    session.add(event)
    await session.flush()
    return event


async def run_write_pipeline(
    session: AsyncSession,
    user_id: str,
    message: str,
    llm: LLMProvider,
    embeddings: EmbeddingProvider,
    config: MemoryConfig,
    source: str = "api",
    recent_messages: list[str] | None = None,
) -> dict:
    """Execute the full write pipeline: extract → resolve → reconcile → upsert.

    Args:
        session: Database session (caller manages transaction/commit).
        user_id: Unique identifier for the user.
        message: The user's message text.
        llm: Injected LLM provider.
        embeddings: Injected embedding provider.
        config: Memory configuration.
        source: Source channel identifier.
        recent_messages: Optional conversation context (last N messages)
            for resolving pronouns and anaphora references in extraction.

    Returns:
        Dict with pipeline results (used by MemoryClient.write to build WriteResult).
    """
    t_start = time.perf_counter()

    # 1. Create immutable event
    event = await _create_event(session, user_id, message, source, embeddings)
    event_id = str(event.id)
    logger.info("write_pipeline: user=%s, event_id=%s", user_id, event_id)

    # Wrap pipeline steps 2-6 in error handling — event is already persisted
    # and survives even if the pipeline fails.
    extraction = ExtractionOutput()
    upsert_result = UpsertResult()

    try:
        # 2. Extract entities, facts, relations (outside savepoint — no DB writes)
        extraction = await extract_from_message(
            message,
            llm=llm,
            model=config.extraction_model,
            extraction_mode=config.extraction_mode,
            config=config,
            recent_messages=recent_messages,
        )

        if not extraction.facts and not extraction.relations:
            duration_ms = round((time.perf_counter() - t_start) * 1000, 1)
            return {
                "event_id": event_id,
                "facts_added": [],
                "facts_updated": [],
                "facts_unchanged": 0,
                "facts_deleted": 0,
                "entities_resolved": [],
                "duration_ms": duration_ms,
            }

        # Steps 3-6 run inside a savepoint so that if anything fails,
        # the partial DB writes are rolled back cleanly but the event
        # (flushed above) survives. Note: inner functions (entity_resolution,
        # reconcile, upsert) also use their own savepoints for per-operation
        # resilience. This outer savepoint provides pipeline-level atomicity.
        async with session.begin_nested():
            # 3. Resolve entities
            entity_map = await resolve_entities(
                session,
                user_id,
                extraction.entities,
                extraction.facts,
                llm=llm,
                embeddings=embeddings,
            )

            # 4. Reconcile facts against existing
            decisions = await reconcile_facts(
                session,
                user_id,
                extraction.facts,
                entity_map,
                llm=llm,
                embeddings=embeddings,
            )

            # 5. Execute decisions (upsert/close/confirm)
            upsert_result = await execute_upsert(
                session,
                user_id,
                event_id,
                decisions,
                entity_map,
                embeddings=embeddings,
            )

            # 6. Upsert relationships
            if extraction.relations:
                await upsert_relations(
                    session,
                    user_id,
                    event_id,
                    extraction.relations,
                    entity_map,
                )

    except Exception as e:
        logger.warning(
            "write_pipeline: failed after event creation: user=%s event=%s error=%s",
            user_id,
            event_id,
            e,
            exc_info=True,
        )
        duration_ms = round((time.perf_counter() - t_start) * 1000, 1)
        return {
            "event_id": event_id,
            "facts_added": [],
            "facts_updated": [],
            "facts_unchanged": 0,
            "facts_deleted": 0,
            "entities_resolved": [],
            "duration_ms": duration_ms,
        }

    duration_ms = round((time.perf_counter() - t_start) * 1000, 1)

    logger.info(
        "write_pipeline: user=%s, event=%s, added=%d, updated=%d, confirmed=%d, entities=%d, latency_ms=%.1f",
        user_id,
        event_id,
        upsert_result.added,
        upsert_result.updated,
        upsert_result.confirmed,
        len(entity_map),
        duration_ms,
    )

    # Build result data
    facts_added = [
        {"fact_text": d.fact_text, "subject": d.subject, "confidence": d.confidence}
        for d in decisions
        if d.action == "ADD"
    ]
    facts_updated = [
        {"fact_text": d.fact_text, "subject": d.subject, "confidence": d.confidence, "replaced": d.matched_fact_text}
        for d in decisions
        if d.action == "UPDATE"
    ]
    entities_resolved = [
        {"name": e.display_name, "key": e.entity_key, "method": e.resolution_method, "is_new": e.is_new}
        for e in entity_map.values()
    ]

    result = {
        "event_id": event_id,
        "facts_added": facts_added,
        "facts_updated": facts_updated,
        "facts_unchanged": upsert_result.confirmed,
        "facts_deleted": upsert_result.deleted,
        "entities_resolved": entities_resolved,
        "duration_ms": duration_ms,
    }

    # Persist trace_json on the event for observability
    try:
        event.trace_json = {
            "extraction_mode": config.extraction_mode,
            "entities_extracted": len(extraction.entities),
            "facts_extracted": len(extraction.facts),
            "relations_extracted": len(extraction.relations),
            "entities_resolved": len(entity_map),
            "decisions": {
                "add": upsert_result.added,
                "update": upsert_result.updated,
                "confirmed": upsert_result.confirmed,
                "deleted": upsert_result.deleted,
            },
            "duration_ms": duration_ms,
        }
    except Exception as e:
        logger.debug("write_pipeline: trace_json persistence failed: %s", e)

    return result
