"""Service layer for CoordTodo."""

from datetime import datetime, timezone
from typing import Optional
from uuid import UUID

from fastapi import HTTPException, status
from sqlalchemy.orm import Session

from api.models.coord_todo import CoordTodo
from api.schemas.coord_todo import CoordTodoCreate, CoordTodoUpdate

_VALID_STATUSES = {"pending", "done", "cancelled"}


def get_todos(
    db: Session,
    project_key: Optional[str] = None,
    assigned_to_user: Optional[str] = None,
    assigned_to_machine: Optional[str] = None,
    for_user: Optional[str] = None,
    for_machine: Optional[str] = None,
    status_filter: str = "pending",
    include_subtasks: bool = True,
    skip: int = 0,
    limit: int = 100,
) -> list[CoordTodo]:
    """Return a flat list of to-do items with optional filters.

    assigned_to_user/machine: exact match (admin queries).
    for_user/for_machine: inclusive match — items for this user/machine OR unassigned.
      Used by sync/save to surface relevant pending work.

    When include_subtasks is False, only top-level items (parent_id IS NULL) are returned.
    """
    from sqlalchemy import or_

    q = db.query(CoordTodo)

    if not include_subtasks:
        q = q.filter(CoordTodo.parent_id.is_(None))

    if project_key is not None:
        q = q.filter(CoordTodo.project_key == project_key)

    if assigned_to_user is not None:
        q = q.filter(CoordTodo.assigned_to_user == assigned_to_user)

    if assigned_to_machine is not None:
        q = q.filter(CoordTodo.assigned_to_machine == assigned_to_machine)

    if for_user is not None:
        q = q.filter(or_(CoordTodo.assigned_to_user == for_user, CoordTodo.assigned_to_user.is_(None)))

    if for_machine is not None:
        q = q.filter(or_(CoordTodo.assigned_to_machine == for_machine, CoordTodo.assigned_to_machine.is_(None)))

    if status_filter != "all":
        q = q.filter(CoordTodo.status == status_filter)

    return q.order_by(CoordTodo.created_at.asc()).offset(skip).limit(limit).all()


def get_todo_by_id(db: Session, todo_id: UUID) -> CoordTodo:
    """Return a single to-do item with its sub-tasks loaded, or raise 404."""
    todo = db.query(CoordTodo).filter(CoordTodo.id == str(todo_id)).first()
    if not todo:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Todo {todo_id} not found"
        )
    todo.subtasks = (
        db.query(CoordTodo)
        .filter(CoordTodo.parent_id == str(todo_id))
        .order_by(CoordTodo.created_at.asc())
        .all()
    )
    return todo


def create_todo(db: Session, todo_in: CoordTodoCreate) -> CoordTodo:
    """Persist a new to-do item."""
    if todo_in.parent_id is not None:
        parent = db.query(CoordTodo).filter(CoordTodo.id == str(todo_in.parent_id)).first()
        if not parent:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail=f"Parent todo {todo_in.parent_id} not found"
            )

    data = todo_in.model_dump()
    if data.get("parent_id") is not None:
        data["parent_id"] = str(data["parent_id"])

    try:
        todo = CoordTodo(**data)
        db.add(todo)
        db.commit()
        db.refresh(todo)
        todo.subtasks = []
        return todo
    except Exception as e:
        db.rollback()
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail=f"Failed to create todo: {e}"
        )


def update_todo(db: Session, todo_id: UUID, todo_in: CoordTodoUpdate) -> CoordTodo:
    """Apply a partial update to a to-do item.

    When status changes to 'done', completed_at is set to the current UTC time.
    When status changes away from 'done', completed_at is cleared.
    """
    todo = get_todo_by_id(db, todo_id)

    patch = todo_in.model_dump(exclude_unset=True)

    if "status" in patch and patch["status"] not in _VALID_STATUSES:
        raise HTTPException(
            status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
            detail=f"Invalid status '{patch['status']}'; must be one of {sorted(_VALID_STATUSES)}"
        )

    for field, value in patch.items():
        setattr(todo, field, value)

    if "status" in patch:
        if patch["status"] == "done" and todo.completed_at is None:
            todo.completed_at = datetime.now(timezone.utc).replace(tzinfo=None)
        elif patch["status"] != "done":
            todo.completed_at = None

    try:
        db.commit()
        db.refresh(todo)
        todo.subtasks = (
            db.query(CoordTodo)
            .filter(CoordTodo.parent_id == str(todo_id))
            .order_by(CoordTodo.created_at.asc())
            .all()
        )
        return todo
    except Exception as e:
        db.rollback()
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail=f"Failed to update todo: {e}"
        )


def delete_todo(db: Session, todo_id: UUID) -> dict:
    """Delete a to-do item and all its sub-tasks.

    Sub-tasks are deleted first to satisfy FK constraints on databases that
    don't enforce CASCADE at the application level.
    """
    todo = get_todo_by_id(db, todo_id)
    try:
        db.query(CoordTodo).filter(CoordTodo.parent_id == str(todo_id)).delete(synchronize_session=False)
        db.delete(todo)
        db.commit()
        return {"message": "Todo deleted", "todo_id": str(todo_id)}
    except Exception as e:
        db.rollback()
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail=f"Failed to delete todo: {e}"
        )
