from copy import deepcopy  # noqa

from fastapi import APIRouter
from fastapi import Depends
from fastapi import HTTPException
from fastapi import Response
from fastapi import status
from sqlmodel import func
from sqlmodel import or_
from sqlmodel import select

from ._aux_functions_tasks import _get_task_full_access
from ._aux_functions_tasks import _get_task_read_access
from ._aux_functions_tasks import _get_valid_user_group_id
from ._aux_functions_tasks import _verify_non_duplication_group_constraint
from ._aux_functions_tasks import _verify_non_duplication_user_constraint
from fractal_server.app.db import AsyncSession
from fractal_server.app.db import get_async_db
from fractal_server.app.models import LinkUserGroup
from fractal_server.app.models import UserOAuth
from fractal_server.app.models.v2 import TaskGroupV2
from fractal_server.app.models.v2 import TaskV2
from fractal_server.app.routes.auth import current_active_user
from fractal_server.app.routes.auth import current_active_verified_user
from fractal_server.app.schemas.v2 import TaskCreateV2
from fractal_server.app.schemas.v2 import TaskGroupV2OriginEnum
from fractal_server.app.schemas.v2 import TaskReadV2
from fractal_server.app.schemas.v2 import TaskType
from fractal_server.app.schemas.v2 import TaskUpdateV2
from fractal_server.logger import set_logger

router = APIRouter()

logger = set_logger(__name__)


@router.get("/", response_model=list[TaskReadV2])
async def get_list_task(
    args_schema: bool = True,
    category: str | None = None,
    modality: str | None = None,
    author: str | None = None,
    user: UserOAuth = Depends(current_active_user),
    db: AsyncSession = Depends(get_async_db),
) -> list[TaskReadV2]:
    """
    Get list of available tasks
    """
    stm = (
        select(TaskV2)
        .join(TaskGroupV2)
        .where(TaskGroupV2.id == TaskV2.taskgroupv2_id)
        .where(
            or_(
                TaskGroupV2.user_id == user.id,
                TaskGroupV2.user_group_id.in_(
                    select(LinkUserGroup.group_id).where(
                        LinkUserGroup.user_id == user.id
                    )
                ),
            )
        )
    )
    if category is not None:
        stm = stm.where(func.lower(TaskV2.category) == category.lower())
    if modality is not None:
        stm = stm.where(func.lower(TaskV2.modality) == modality.lower())
    if author is not None:
        stm = stm.where(TaskV2.authors.icontains(author))

    res = await db.execute(stm)
    task_list = res.scalars().all()
    await db.close()
    if args_schema is False:
        for task in task_list:
            setattr(task, "args_schema_parallel", None)
            setattr(task, "args_schema_non_parallel", None)

    return task_list


@router.get("/{task_id}/", response_model=TaskReadV2)
async def get_task(
    task_id: int,
    user: UserOAuth = Depends(current_active_user),
    db: AsyncSession = Depends(get_async_db),
) -> TaskReadV2:
    """
    Get info on a specific task
    """
    task = await _get_task_read_access(task_id=task_id, user_id=user.id, db=db)
    return task


@router.patch("/{task_id}/", response_model=TaskReadV2)
async def patch_task(
    task_id: int,
    task_update: TaskUpdateV2,
    user: UserOAuth = Depends(current_active_verified_user),
    db: AsyncSession = Depends(get_async_db),
) -> TaskReadV2 | None:
    """
    Edit a specific task (restricted to task owner)
    """

    # Retrieve task from database
    db_task = await _get_task_full_access(
        task_id=task_id, user_id=user.id, db=db
    )
    update = task_update.model_dump(exclude_unset=True)

    # Forbid changes that set a previously unset command
    if db_task.type == TaskType.NON_PARALLEL and "command_parallel" in update:
        raise HTTPException(
            status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
            detail="Cannot set an unset `command_parallel`.",
        )
    if db_task.type == TaskType.PARALLEL and "command_non_parallel" in update:
        raise HTTPException(
            status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
            detail="Cannot set an unset `command_non_parallel`.",
        )

    for key, value in update.items():
        setattr(db_task, key, value)

    await db.commit()
    await db.refresh(db_task)
    await db.close()
    return db_task


@router.post(
    "/", response_model=TaskReadV2, status_code=status.HTTP_201_CREATED
)
async def create_task(
    task: TaskCreateV2,
    user_group_id: int | None = None,
    private: bool = False,
    user: UserOAuth = Depends(current_active_verified_user),
    db: AsyncSession = Depends(get_async_db),
) -> TaskReadV2 | None:
    """
    Create a new task
    """

    # Validate query parameters related to user-group ownership
    user_group_id = await _get_valid_user_group_id(
        user_group_id=user_group_id,
        private=private,
        user_id=user.id,
        db=db,
    )

    if task.type == TaskType.PARALLEL and (
        task.args_schema_non_parallel is not None
        or task.meta_non_parallel is not None
    ):
        raise HTTPException(
            status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
            detail=(
                "Cannot set `TaskV2.args_schema_non_parallel` or "
                "`TaskV2.args_schema_non_parallel` if TaskV2 is parallel"
            ),
        )
    elif task.type == TaskType.NON_PARALLEL and (
        task.args_schema_parallel is not None or task.meta_parallel is not None
    ):
        raise HTTPException(
            status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
            detail=(
                "Cannot set `TaskV2.args_schema_parallel` or "
                "`TaskV2.args_schema_parallel` if TaskV2 is non_parallel"
            ),
        )

    # Add task

    db_task = TaskV2(**task.model_dump(exclude_unset=True))
    pkg_name = db_task.name
    await _verify_non_duplication_user_constraint(
        db=db, pkg_name=pkg_name, user_id=user.id, version=db_task.version
    )
    await _verify_non_duplication_group_constraint(
        db=db,
        pkg_name=pkg_name,
        user_group_id=user_group_id,
        version=db_task.version,
    )
    db_task_group = TaskGroupV2(
        user_id=user.id,
        user_group_id=user_group_id,
        active=True,
        task_list=[db_task],
        origin=TaskGroupV2OriginEnum.OTHER,
        version=db_task.version,
        pkg_name=pkg_name,
    )
    db.add(db_task_group)
    await db.commit()
    await db.refresh(db_task)
    await db.close()

    return db_task


@router.delete("/{task_id}/", status_code=204)
async def delete_task(
    task_id: int,
    user: UserOAuth = Depends(current_active_user),
    db: AsyncSession = Depends(get_async_db),
) -> Response:
    """
    Delete a task
    """
    raise HTTPException(
        status_code=status.HTTP_405_METHOD_NOT_ALLOWED,
        detail=(
            "Cannot delete single tasks, "
            "please operate directly on task groups."
        ),
    )
