"""Workspace CRUD API endpoints."""

import logging
import uuid

from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy import func, select
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload

from src.auth.dependencies import get_current_user_id, get_db_session
from src.models import GitRepository
from src.models import ToolInstance
from src.models import Workspace
from src.services.shared.workspace_manager import WorkspaceHasInstancesError, WorkspaceManager

logger = logging.getLogger(__name__)

router = APIRouter(prefix="/projects/{project_id}/repositories/{repo_id}/workspaces")
all_workspaces_router = APIRouter(prefix="/workspaces")


@all_workspaces_router.get("/")
async def list_all_workspaces(
    user_id: uuid.UUID = Depends(get_current_user_id),
    session: AsyncSession = Depends(get_db_session),
) -> list[dict]:
    """List all workspaces for the current user across all repos."""
    instance_count = (
        select(func.count(ToolInstance.id))
        .where(ToolInstance.workspace_id == Workspace.id)
        .correlate(Workspace)
        .scalar_subquery()
    )

    result = await session.execute(
        select(
            Workspace,
            GitRepository,
            instance_count.label("instance_count"),
        )
        .join(GitRepository, Workspace.repo_id == GitRepository.id)
        .options(selectinload(GitRepository.project))
        .where(Workspace.user_id == user_id)
        .order_by(Workspace.created_at.desc())
    )
    rows = result.all()

    return [
        {
            "id": str(ws.id),
            "name": ws.name,
            "repo_id": str(repo.id),
            "repo_name": repo.name or "",
            "repo_ssh_key_id": str(repo.ssh_key_id) if repo.ssh_key_id else None,
            "project_id": str(repo.project_id) if repo.project_id else "",
            "project_name": repo.project.name if repo.project else "",
            "user_id": str(ws.user_id),
            "branch": ws.branch,
            "path": ws.path,
            "status": ws.status,
            "last_sync_at": ws.last_sync_at.isoformat() if ws.last_sync_at else None,
            "created_at": ws.created_at.isoformat() if ws.created_at else None,
            "updated_at": ws.updated_at.isoformat() if ws.updated_at else None,
            "instance_count": count or 0,
        }
        for ws, repo, count in rows
    ]


@all_workspaces_router.delete("/{workspace_id}")
async def delete_workspace_top_level(
    workspace_id: uuid.UUID,
    force: bool = Query(False),
    user_id: uuid.UUID = Depends(get_current_user_id),
    session: AsyncSession = Depends(get_db_session),
) -> dict:
    """Delete a workspace via top-level path."""
    workspace = await session.get(Workspace, workspace_id)
    if not workspace or workspace.user_id != user_id:
        raise HTTPException(status_code=404, detail="Workspace not found")

    manager = WorkspaceManager()
    try:
        await manager.delete(workspace, force=force, session=session)
        await session.commit()
    except WorkspaceHasInstancesError as exc:
        await session.rollback()
        raise HTTPException(
            status_code=409,
            detail={
                "message": "Workspace has running tool instances",
                "instances": exc.instances,
            },
        ) from exc
    except Exception as exc:
        await session.rollback()
        logger.error("Failed to delete workspace: %s", exc)
        raise HTTPException(
            status_code=500, detail="Failed to delete workspace"
        ) from exc

    return {"status": "deleted"}


@all_workspaces_router.post("/")
async def create_workspace_top_level(
    data: dict,
    user_id: uuid.UUID = Depends(get_current_user_id),
    session: AsyncSession = Depends(get_db_session),
) -> dict:
    """Create a workspace directly (no nested project/repo path)."""
    repo_id_str = data.get("repo_id", "").strip()
    if not repo_id_str:
        raise HTTPException(status_code=400, detail="repo_id is required")

    try:
        repo_id = uuid.UUID(repo_id_str)
    except ValueError as exc:
        raise HTTPException(status_code=400, detail="Invalid repo_id format") from exc

    repo = await session.get(GitRepository, repo_id)
    if not repo or repo.owner_id != user_id:
        raise HTTPException(status_code=404, detail="Repository not found")

    name = data.get("name", "").strip()
    branch = data.get("branch", "main").strip()

    if not name:
        raise HTTPException(status_code=400, detail="Workspace name is required")

    manager = WorkspaceManager()
    try:
        workspace = await manager.create(repo, user_id, name, branch, session=session)
        session.add(workspace)
        await session.commit()
    except Exception as exc:
        await session.rollback()
        logger.error("Failed to create workspace: %s", exc)
        raise HTTPException(
            status_code=409,
            detail="Workspace name already exists for this repository",
        ) from exc

    await session.refresh(workspace)
    return {
        "id": str(workspace.id),
        "name": workspace.name,
        "repo_id": str(workspace.repo_id),
        "branch": workspace.branch,
        "path": workspace.path,
        "status": workspace.status,
        "created_at": workspace.created_at.isoformat()
        if workspace.created_at
        else None,
    }


@router.get("/")
async def list_workspaces(
    project_id: uuid.UUID,
    repo_id: uuid.UUID,
    user_id: uuid.UUID = Depends(get_current_user_id),
    session: AsyncSession = Depends(get_db_session),
) -> list[dict]:
    """List workspaces for a repository, with instance counts."""
    # Verify repo belongs to project and user
    repo = await _get_repo(session, repo_id, project_id, user_id)

    # Build subquery for instance counts
    instance_count = (
        select(func.count(ToolInstance.id))
        .where(ToolInstance.workspace_id == Workspace.id)
        .correlate(Workspace)
        .scalar_subquery()
    )

    result = await session.execute(
        select(
            Workspace,
            instance_count.label("instance_count"),
        )
        .where(Workspace.repo_id == repo_id)
        .order_by(Workspace.created_at.desc())
    )
    rows = result.all()

    return [
        {
            "id": str(ws.id),
            "name": ws.name,
            "repo_id": str(ws.repo_id),
            "repo_name": repo.name,
            "repo_ssh_key_id": str(repo.ssh_key_id) if repo.ssh_key_id else None,
            "project_id": str(repo.project_id) if repo.project_id else "",
            "project_name": repo.project.name if repo.project else "",
            "user_id": str(ws.user_id),
            "branch": ws.branch,
            "path": ws.path,
            "status": ws.status,
            "last_sync_at": ws.last_sync_at.isoformat() if ws.last_sync_at else None,
            "created_at": ws.created_at.isoformat() if ws.created_at else None,
            "updated_at": ws.updated_at.isoformat() if ws.updated_at else None,
            "instance_count": count or 0,
        }
        for ws, count in rows
    ]


@router.post("/")
async def create_workspace(
    project_id: uuid.UUID,
    repo_id: uuid.UUID,
    data: dict,
    user_id: uuid.UUID = Depends(get_current_user_id),
    session: AsyncSession = Depends(get_db_session),
) -> dict:
    """Create a new workspace by cloning a repository branch."""
    repo = await _get_repo(session, repo_id, project_id, user_id)

    name = data.get("name", "").strip()
    branch = data.get("branch", "main").strip()

    if not name:
        raise HTTPException(status_code=400, detail="Workspace name is required")
    if not branch:
        raise HTTPException(status_code=400, detail="Branch is required")

    manager = WorkspaceManager()
    try:
        workspace = await manager.create(repo, user_id, name, branch, session=session)
        session.add(workspace)
        await session.commit()
    except HTTPException:
        raise
    except ValueError as exc:
        await session.rollback()
        logger.error("Failed to create workspace: %s", exc)
        raise HTTPException(status_code=400, detail=str(exc)) from exc
    except Exception as exc:
        await session.rollback()
        logger.error("Failed to create workspace: %s", exc)
        raise HTTPException(
            status_code=409,
            detail="Workspace name already exists for this repository",
        ) from exc

    await session.refresh(workspace)
    return {
        "id": str(workspace.id),
        "name": workspace.name,
        "repo_id": str(workspace.repo_id),
        "branch": workspace.branch,
        "path": workspace.path,
        "status": workspace.status,
        "created_at": workspace.created_at.isoformat()
        if workspace.created_at
        else None,
    }


@router.get("/{workspace_id}")
async def get_workspace_detail(
    project_id: uuid.UUID,
    repo_id: uuid.UUID,
    workspace_id: uuid.UUID,
    user_id: uuid.UUID = Depends(get_current_user_id),
    session: AsyncSession = Depends(get_db_session),
) -> dict:
    """Get workspace details."""
    repo = await _get_repo(session, repo_id, project_id, user_id)
    workspace = await _get_workspace(session, workspace_id, repo_id)

    # Count instances
    result = await session.execute(
        select(func.count(ToolInstance.id)).where(
            ToolInstance.workspace_id == workspace_id
        )
    )
    instance_count = result.scalar() or 0

    return {
        "id": str(workspace.id),
        "name": workspace.name,
        "repo_id": str(workspace.repo_id),
        "repo_name": repo.name,
        "user_id": str(workspace.user_id),
        "branch": workspace.branch,
        "path": workspace.path,
        "status": workspace.status,
        "last_sync_at": workspace.last_sync_at.isoformat()
        if workspace.last_sync_at
        else None,
        "created_at": workspace.created_at.isoformat()
        if workspace.created_at
        else None,
        "updated_at": workspace.updated_at.isoformat()
        if workspace.updated_at
        else None,
        "instance_count": instance_count,
    }


@router.patch("/{workspace_id}")
async def update_workspace(
    project_id: uuid.UUID,
    repo_id: uuid.UUID,
    workspace_id: uuid.UUID,
    data: dict,
    user_id: uuid.UUID = Depends(get_current_user_id),
    session: AsyncSession = Depends(get_db_session),
) -> dict:
    """Update workspace name or branch."""
    await _get_repo(session, repo_id, project_id, user_id)
    workspace = await _get_workspace(session, workspace_id, repo_id)

    new_name = data.get("name", "").strip()
    new_branch = data.get("branch", "").strip()

    if new_name:
        workspace.name = new_name
    if new_branch:
        workspace.branch = new_branch

    try:
        await session.commit()
    except Exception as exc:
        await session.rollback()
        logger.error("Failed to update workspace: %s", exc)
        raise HTTPException(
            status_code=409,
            detail="Workspace name already exists for this repository",
        ) from exc

    return {
        "id": str(workspace.id),
        "name": workspace.name,
        "branch": workspace.branch,
        "status": workspace.status,
    }


@router.delete("/{workspace_id}")
async def delete_workspace(
    project_id: uuid.UUID,
    repo_id: uuid.UUID,
    workspace_id: uuid.UUID,
    force: bool = Query(False),
    user_id: uuid.UUID = Depends(get_current_user_id),
    session: AsyncSession = Depends(get_db_session),
) -> dict:
    """Delete a workspace. Returns 409 if instances exist and force=False."""
    await _get_repo(session, repo_id, project_id, user_id)
    workspace = await _get_workspace(session, workspace_id, repo_id)

    manager = WorkspaceManager()
    try:
        await manager.delete(workspace, force=force, session=session)
        await session.commit()
    except WorkspaceHasInstancesError as exc:
        await session.rollback()
        raise HTTPException(
            status_code=409,
            detail={
                "message": "Workspace has running tool instances",
                "instances": exc.instances,
            },
        ) from exc
    except Exception as exc:
        await session.rollback()
        logger.error("Failed to delete workspace: %s", exc)
        raise HTTPException(
            status_code=500, detail="Failed to delete workspace"
        ) from exc

    return {"status": "deleted"}


@router.post("/{workspace_id}/sync")
async def sync_workspace(
    project_id: uuid.UUID,
    repo_id: uuid.UUID,
    workspace_id: uuid.UUID,
    user_id: uuid.UUID = Depends(get_current_user_id),
    session: AsyncSession = Depends(get_db_session),
) -> dict:
    """Sync workspace with remote. Returns 409 if branch was deleted."""
    await _get_repo(session, repo_id, project_id, user_id)
    workspace = await _get_workspace(session, workspace_id, repo_id)

    manager = WorkspaceManager()
    result = await manager.sync(workspace, session=session)

    if result.branch_deleted:
        raise HTTPException(
            status_code=409,
            detail={
                "message": f"Branch '{workspace.branch}' was deleted from remote",
                "branch_deleted": True,
            },
        )

    await session.commit()
    return {
        "branch_deleted": False,
        "pulled": True,
        "last_sync_at": workspace.last_sync_at.isoformat()
        if workspace.last_sync_at
        else None,
    }


async def _get_repo(
    session: AsyncSession,
    repo_id: uuid.UUID,
    project_id: uuid.UUID,
    user_id: uuid.UUID,
) -> GitRepository:
    """Fetch and validate repository access."""
    result = await session.execute(
        select(GitRepository)
        .where(
            GitRepository.id == repo_id,
            GitRepository.project_id == project_id,
        )
        .options(selectinload(GitRepository.project))
    )
    repo = result.scalar_one_or_none()
    if not repo:
        raise HTTPException(status_code=404, detail="Repository not found")
    return repo


async def _get_workspace(
    session: AsyncSession,
    workspace_id: uuid.UUID,
    repo_id: uuid.UUID,
) -> Workspace:
    """Fetch and validate workspace."""
    result = await session.execute(
        select(Workspace).where(
            Workspace.id == workspace_id,
            Workspace.repo_id == repo_id,
        )
    )
    workspace = result.scalar_one_or_none()
    if not workspace:
        raise HTTPException(status_code=404, detail="Workspace not found")
    return workspace
