"""Moment CRUD, entity linking, and embedding sync.

Moments are small structured extractions from journal conversations.
Stored separately from Notes — they have their own embedding index and
the isolation between notes-RAG and journal-RAG is enforced at the API
boundary, not just the schema.
"""
from __future__ import annotations

import datetime
from typing import Sequence

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

from fabledassistant.models import (
    Moment,
    MomentEmbedding,
    async_session,
    moment_notes,
    moment_people,
    moment_places,
    moment_tasks,
)
from fabledassistant.services.embeddings import get_embedding


async def create_moment(
    *,
    user_id: int,
    content: str,
    occurred_at: datetime.datetime,
    day_date: datetime.date,
    conversation_id: int | None = None,
    source_message_id: int | None = None,
    raw_excerpt: str | None = None,
    tags: Sequence[str] = (),
    person_ids: Sequence[int] = (),
    place_ids: Sequence[int] = (),
    task_ids: Sequence[int] = (),
    note_ids: Sequence[int] = (),
) -> Moment:
    """Insert a Moment, write its embedding, create junction rows."""
    async with async_session() as session:
        moment = Moment(
            user_id=user_id,
            conversation_id=conversation_id,
            source_message_id=source_message_id,
            day_date=day_date,
            occurred_at=occurred_at,
            content=content,
            raw_excerpt=raw_excerpt,
            tags=list(tags),
        )
        session.add(moment)
        await session.flush()

        await _set_links(
            session,
            moment_id=moment.id,
            person_ids=person_ids,
            place_ids=place_ids,
            task_ids=task_ids,
            note_ids=note_ids,
        )

        embedding_vector = await get_embedding(content)
        session.add(
            MomentEmbedding(
                moment_id=moment.id,
                user_id=user_id,
                embedding=embedding_vector,
            )
        )

        await session.commit()
        await session.refresh(moment, ["people", "places", "tasks", "notes"])
        return moment


async def get_moment(*, user_id: int, moment_id: int) -> Moment | None:
    async with async_session() as session:
        result = await session.execute(
            select(Moment).where(Moment.id == moment_id, Moment.user_id == user_id)
        )
        return result.scalar_one_or_none()


async def update_moment(
    *,
    user_id: int,
    moment_id: int,
    content: str | None = None,
    tags: Sequence[str] | None = None,
    pinned: bool | None = None,
    person_ids: Sequence[int] | None = None,
    place_ids: Sequence[int] | None = None,
    task_ids: Sequence[int] | None = None,
    note_ids: Sequence[int] | None = None,
) -> Moment | None:
    """Update fields and (optionally) replace junction sets.

    None for a junction parameter ⇒ leave unchanged. Empty sequence ⇒ clear.
    Mirrors the Notes update convention.
    """
    async with async_session() as session:
        result = await session.execute(
            select(Moment).where(Moment.id == moment_id, Moment.user_id == user_id)
        )
        moment = result.scalar_one_or_none()
        if moment is None:
            return None

        if content is not None:
            moment.content = content
            embedding_vector = await get_embedding(content)
            await session.execute(
                delete(MomentEmbedding).where(MomentEmbedding.moment_id == moment_id)
            )
            session.add(
                MomentEmbedding(
                    moment_id=moment_id,
                    user_id=user_id,
                    embedding=embedding_vector,
                )
            )
        if tags is not None:
            moment.tags = list(tags)
        if pinned is not None:
            moment.pinned = pinned

        await _set_links(
            session,
            moment_id=moment_id,
            person_ids=person_ids,
            place_ids=place_ids,
            task_ids=task_ids,
            note_ids=note_ids,
        )

        await session.commit()
        await session.refresh(moment, ["people", "places", "tasks", "notes"])
        return moment


async def delete_moment(*, user_id: int, moment_id: int) -> bool:
    async with async_session() as session:
        result = await session.execute(
            select(Moment).where(Moment.id == moment_id, Moment.user_id == user_id)
        )
        moment = result.scalar_one_or_none()
        if moment is None:
            return False
        await session.delete(moment)
        await session.commit()
        return True


async def _set_links(
    session: AsyncSession,
    *,
    moment_id: int,
    person_ids: Sequence[int] | None,
    place_ids: Sequence[int] | None,
    task_ids: Sequence[int] | None,
    note_ids: Sequence[int] | None,
) -> None:
    """Replace junction-table rows.

    None ⇒ leave existing rows alone. Empty sequence ⇒ clear all rows.
    """
    for ids, table, fk_col in [
        (person_ids, moment_people, "person_id"),
        (place_ids, moment_places, "place_id"),
        (task_ids, moment_tasks, "task_id"),
        (note_ids, moment_notes, "note_id"),
    ]:
        if ids is None:
            continue
        await session.execute(delete(table).where(table.c.moment_id == moment_id))
        if ids:
            await session.execute(
                table.insert(),
                [{"moment_id": moment_id, fk_col: i} for i in ids],
            )
