"""The running list of things to raise with the client, and every comment thread.

Authorship comes from the session, never the payload: each write stores the EFFECTIVE
account's id in the who-column and its display name as a snapshot (the raw record). The
`item:*` threads belong to the running list on /plan: the list read filters them out, and a
post or edit on one answers 404, unless the caller holds `plan.view` (the same
non-disclosure as the /plan page). A comment is editable by its author under
`client.participate` and by anyone under `discussion.curate`.
"""

from fastapi import APIRouter, Depends, HTTPException, Request
from sqlalchemy import func, select
from sqlalchemy.dialects.postgresql import insert as pg_insert
from sqlalchemy.orm import Session, selectinload

from app.db import get_db
from app.models import DiscussionComment, DiscussionItem, FeaturePriority
from app.services import accounts, identity
from app.models.schemas import (
    DiscussionCommentIn,
    DiscussionCommentOut,
    DiscussionItemIn,
    DiscussionItemOut,
    FeatureCommentEdit,
    FeatureCommentIn,
    FeatureCommentOut,
    FeaturePriorityIn,
    FeaturePriorityOut,
)

router = APIRouter(prefix="/api/discussion", tags=["discussion"])

ITEM_PREFIX = "item:"


def _sees_items(request: Request) -> bool:
    return accounts.can(identity.optional_user(request), accounts.PERM_PLAN_VIEW)


def _not_found() -> HTTPException:
    return HTTPException(status_code=404, detail="no such comment")


@router.get("/feature-comments", response_model=list[FeatureCommentOut])
def list_feature_comments(request: Request, db: Session = Depends(get_db)) -> list[DiscussionComment]:
    """Every comment left on a keyed thread, oldest first; the running list's `item:*`
    threads only for a caller who may see /plan."""
    query = select(DiscussionComment).where(DiscussionComment.feature_key.isnot(None))
    if not _sees_items(request):
        query = query.where(DiscussionComment.feature_key.notlike(ITEM_PREFIX + "%"))
    return list(db.scalars(query.order_by(DiscussionComment.created_at, DiscussionComment.id)))


@router.post("/feature-comments", response_model=FeatureCommentOut, status_code=201)
def add_feature_comment(
    payload: FeatureCommentIn, request: Request, db: Session = Depends(get_db)
) -> DiscussionComment:
    if payload.feature_key.startswith(ITEM_PREFIX) and not _sees_items(request):
        raise _not_found()
    who = identity.actor(request)
    comment = DiscussionComment(
        feature_key=payload.feature_key, author=who.display_name, author_id=who.id, body=payload.body
    )
    db.add(comment)
    db.commit()
    db.refresh(comment)
    return comment


@router.post("/feature-comments/{comment_id}", response_model=FeatureCommentOut)
def edit_feature_comment(
    comment_id: int, payload: FeatureCommentEdit, request: Request, db: Session = Depends(get_db)
) -> DiscussionComment:
    """Rewrite the body of a comment: one's own under client.participate, anyone's under
    discussion.curate. The author is never editable and `updated_at` moves, so an edited
    comment says so."""
    comment = db.get(DiscussionComment, comment_id)
    if comment is None or comment.feature_key is None:
        raise _not_found()
    if comment.feature_key.startswith(ITEM_PREFIX) and not _sees_items(request):
        raise _not_found()
    who = identity.actor(request)
    curator = accounts.can(who.username, accounts.PERM_DISCUSSION_CURATE)
    if comment.author_id != who.id and not curator:
        raise HTTPException(
            status_code=403,
            detail={"error_code": "FORBIDDEN", "summary": "You can edit your own comments only."},
        )
    comment.body = payload.body
    db.commit()
    db.refresh(comment)
    return comment


@router.get("/feature-priorities", response_model=list[FeaturePriorityOut])
def list_feature_priorities(db: Session = Depends(get_db)) -> list[FeaturePriority]:
    """The client's current demo-scope ranking, one row per feature."""
    return list(db.scalars(select(FeaturePriority).order_by(FeaturePriority.feature_key)))


@router.post("/feature-priorities", response_model=FeaturePriorityOut)
def set_feature_priority(
    payload: FeaturePriorityIn, request: Request, db: Session = Depends(get_db)
) -> FeaturePriority:
    """Upsert: the latest choice per feature wins; the client can change his mind.

    ON CONFLICT so two simultaneous first choices for one feature (a fast
    double-tap, or two viewers) cannot race a SELECT-then-INSERT into a 500.
    """
    who = identity.actor(request)
    stmt = (
        pg_insert(FeaturePriority)
        .values(
            feature_key=payload.feature_key,
            priority=payload.priority,
            author=who.display_name,
            author_id=who.id,
        )
        .on_conflict_do_update(
            index_elements=[FeaturePriority.feature_key],
            set_={"priority": payload.priority, "author": who.display_name, "author_id": who.id,
                  "updated_at": func.now()},
        )
    )
    db.execute(stmt)
    db.commit()
    row = db.scalar(
        select(FeaturePriority).where(FeaturePriority.feature_key == payload.feature_key)
    )
    assert row is not None
    return row


@router.get("", response_model=list[DiscussionItemOut])
def list_items(db: Session = Depends(get_db)) -> list[DiscussionItem]:
    return list(
        db.scalars(
            select(DiscussionItem)
            .options(selectinload(DiscussionItem.comments))
            .order_by(
                DiscussionItem.resolved, DiscussionItem.sort_order, DiscussionItem.id
            )
        )
    )


@router.post("", response_model=DiscussionItemOut, status_code=201)
def add_item(payload: DiscussionItemIn, db: Session = Depends(get_db)) -> DiscussionItem:
    title = payload.title.strip()
    if not title:
        raise HTTPException(status_code=422, detail={"error_code": "TITLE_REQUIRED"})
    item = DiscussionItem(title=title, note=(payload.note or "").strip() or None)
    db.add(item)
    db.commit()
    return item


@router.post("/{item_id}", response_model=DiscussionItemOut)
def update_item(
    item_id: int, payload: DiscussionItemIn, db: Session = Depends(get_db)
) -> DiscussionItem:
    """Reword an item. The list must track reality, and reality moves."""
    item = db.get(DiscussionItem, item_id)
    if item is None:
        raise HTTPException(status_code=404, detail={"error_code": "ITEM_NOT_FOUND"})
    title = payload.title.strip()
    if not title:
        raise HTTPException(status_code=422, detail={"error_code": "TITLE_REQUIRED"})
    item.title = title
    item.note = (payload.note or "").strip() or None
    if payload.theme is not None:
        item.theme = payload.theme.strip() or None
    if payload.needs_decision is not None:
        item.needs_decision = payload.needs_decision
    if payload.sort_order is not None:
        item.sort_order = payload.sort_order
    db.commit()
    return item


@router.post("/{item_id}/resolved", response_model=DiscussionItemOut)
def set_resolved(item_id: int, resolved: bool, db: Session = Depends(get_db)) -> DiscussionItem:
    item = db.get(DiscussionItem, item_id)
    if item is None:
        raise HTTPException(status_code=404, detail={"error_code": "ITEM_NOT_FOUND"})
    item.resolved = resolved
    db.commit()
    return item


@router.post("/{item_id}/comments", response_model=DiscussionCommentOut, status_code=201)
def add_comment(
    item_id: int, payload: DiscussionCommentIn, request: Request, db: Session = Depends(get_db)
) -> DiscussionComment:
    """The client's side of the conversation, under the signed-in account's name."""
    item = db.get(DiscussionItem, item_id)
    if item is None:
        raise HTTPException(status_code=404, detail={"error_code": "ITEM_NOT_FOUND"})
    body = payload.body.strip()[:2000]
    if not body:
        raise HTTPException(status_code=422, detail={"error_code": "BODY_REQUIRED"})
    who = identity.actor(request)
    comment = DiscussionComment(item_id=item.id, author=who.display_name, author_id=who.id, body=body)
    db.add(comment)
    db.commit()
    return comment
