import uuid
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, delete, func
from sqlalchemy.orm import selectinload
from notes.orm import Entry, EntryLink
from notes.models import EntryCreate, EntryUpdate, EntryLinkIn
from shared.exceptions import NotFoundError


async def create_entry(db: AsyncSession, data: EntryCreate, author_id: str, author_name: str) -> Entry:
    entry = Entry(
        type=data.type,
        title=data.title,
        body=data.body,
        status=data.status if data.type == "issue" else None,
        severity=data.severity if data.type == "issue" else None,
        category=data.category if data.type == "issue" else None,
        author_id=author_id,
        author_name=author_name,
    )
    db.add(entry)
    await db.flush()  # get the ID before inserting links

    for link_in in data.links:
        db.add(EntryLink(entry_id=entry.id, entity_type=link_in.entity_type, entity_id=link_in.entity_id))

    await db.commit()
    await db.refresh(entry)
    return await _get_entry_with_links(db, entry.id)


async def get_entry(db: AsyncSession, entry_id: uuid.UUID) -> Entry:
    return await _get_entry_with_links(db, entry_id)


async def list_entries(
    db: AsyncSession,
    type: str | None, status: str | None, severity: str | None, category: str | None,
    page: int, limit: int,
) -> tuple[list[Entry], int]:
    limit  = min(100, max(1, limit))
    offset = (max(1, page) - 1) * limit

    q = select(Entry).options(selectinload(Entry.links))
    if type:     q = q.where(Entry.type == type)
    if status:   q = q.where(Entry.status == status)
    if severity: q = q.where(Entry.severity == severity)
    if category: q = q.where(Entry.category == category)

    total_q = select(func.count()).select_from(q.subquery())
    total   = (await db.execute(total_q)).scalar()
    rows    = (await db.execute(q.order_by(Entry.created_at.desc()).limit(limit).offset(offset))).scalars().all()
    return rows, total


async def update_entry(db: AsyncSession, entry_id: uuid.UUID, data: EntryUpdate) -> Entry:
    entry = await _get_entry_with_links(db, entry_id)
    for field, value in data.model_dump(exclude_unset=True).items():
        setattr(entry, field, value)
    await db.commit()
    return await _get_entry_with_links(db, entry_id)


async def delete_entry(db: AsyncSession, entry_id: uuid.UUID):
    entry = await _get_entry_with_links(db, entry_id)
    await db.delete(entry)
    await db.commit()


async def replace_links(db: AsyncSession, entry_id: uuid.UUID, links: list[EntryLinkIn]) -> Entry:
    await _get_entry_with_links(db, entry_id)  # raises 404 if not found
    await db.execute(delete(EntryLink).where(EntryLink.entry_id == entry_id))
    for link_in in links:
        db.add(EntryLink(entry_id=entry_id, entity_type=link_in.entity_type, entity_id=link_in.entity_id))
    await db.commit()
    return await _get_entry_with_links(db, entry_id)


async def list_entries_for_entity(db: AsyncSession, entity_type: str, entity_id: str) -> list[Entry]:
    link_sq = select(EntryLink.entry_id).where(
        EntryLink.entity_type == entity_type,
        EntryLink.entity_id == entity_id,
    ).subquery()
    q = select(Entry).options(selectinload(Entry.links)).where(Entry.id.in_(select(link_sq)))
    return (await db.execute(q.order_by(Entry.created_at.desc()))).scalars().all()


async def _get_entry_with_links(db: AsyncSession, entry_id: uuid.UUID) -> Entry:
    q = select(Entry).options(selectinload(Entry.links)).where(Entry.id == entry_id)
    result = (await db.execute(q)).scalar_one_or_none()
    if not result:
        raise NotFoundError("Entry")
    return result
