# app/api/routes/issues.py
"""
Issue CRUD API endpoints for Syndarix projects.

Provides endpoints for managing issues within projects, including:
- Create, read, update, delete operations
- Filtering by status, priority, labels, sprint, assigned agent
- Search across title and body
- Assignment to agents
- External issue tracker sync triggers
"""

import logging
import os
from typing import Any
from uuid import UUID

from fastapi import APIRouter, Depends, Query, Request, status
from slowapi import Limiter
from slowapi.util import get_remote_address
from sqlalchemy.ext.asyncio import AsyncSession

from app.api.dependencies.auth import get_current_user
from app.core.database import get_db
from app.core.exceptions import (
    AuthorizationError,
    NotFoundError,
    ValidationException,
)
from app.crud.syndarix.agent_instance import agent_instance as agent_instance_crud
from app.crud.syndarix.issue import issue as issue_crud
from app.crud.syndarix.project import project as project_crud
from app.crud.syndarix.sprint import sprint as sprint_crud
from app.models.syndarix.enums import IssuePriority, IssueStatus, SyncStatus
from app.models.user import User
from app.schemas.common import (
    MessageResponse,
    PaginatedResponse,
    PaginationParams,
    SortOrder,
    create_pagination_meta,
)
from app.schemas.errors import ErrorCode
from app.schemas.syndarix.issue import (
    IssueAssign,
    IssueCreate,
    IssueResponse,
    IssueStats,
    IssueUpdate,
)

router = APIRouter()
logger = logging.getLogger(__name__)

# Initialize limiter for this router
limiter = Limiter(key_func=get_remote_address)

# Use higher rate limits in test environment
IS_TEST = os.getenv("IS_TEST", "False") == "True"
RATE_MULTIPLIER = 100 if IS_TEST else 1


async def verify_project_ownership(
    db: AsyncSession,
    project_id: UUID,
    user: User,
) -> None:
    """
    Verify that the user owns the project or is a superuser.

    Args:
        db: Database session
        project_id: Project UUID to verify
        user: Current authenticated user

    Raises:
        NotFoundError: If project does not exist
        AuthorizationError: If user does not own the project
    """
    project = await project_crud.get(db, id=project_id)
    if not project:
        raise NotFoundError(
            message=f"Project {project_id} not found",
            error_code=ErrorCode.NOT_FOUND,
        )

    if not user.is_superuser and project.owner_id != user.id:
        raise AuthorizationError(
            message="You do not have access to this project",
            error_code=ErrorCode.INSUFFICIENT_PERMISSIONS,
        )


def _build_issue_response(
    issue: Any,
    project_name: str | None = None,
    project_slug: str | None = None,
    sprint_name: str | None = None,
    assigned_agent_type_name: str | None = None,
) -> IssueResponse:
    """
    Build an IssueResponse from an Issue model instance.

    Args:
        issue: Issue model instance
        project_name: Optional project name from relationship
        project_slug: Optional project slug from relationship
        sprint_name: Optional sprint name from relationship
        assigned_agent_type_name: Optional agent type name from relationship

    Returns:
        IssueResponse schema instance
    """
    return IssueResponse(
        id=issue.id,
        project_id=issue.project_id,
        title=issue.title,
        body=issue.body,
        status=issue.status,
        priority=issue.priority,
        labels=issue.labels or [],
        assigned_agent_id=issue.assigned_agent_id,
        human_assignee=issue.human_assignee,
        sprint_id=issue.sprint_id,
        story_points=issue.story_points,
        external_tracker_type=issue.external_tracker_type,
        external_issue_id=issue.external_issue_id,
        remote_url=issue.remote_url,
        external_issue_number=issue.external_issue_number,
        sync_status=issue.sync_status,
        last_synced_at=issue.last_synced_at,
        external_updated_at=issue.external_updated_at,
        closed_at=issue.closed_at,
        created_at=issue.created_at,
        updated_at=issue.updated_at,
        project_name=project_name,
        project_slug=project_slug,
        sprint_name=sprint_name,
        assigned_agent_type_name=assigned_agent_type_name,
    )


# ===== Issue CRUD Endpoints =====


@router.post(
    "/projects/{project_id}/issues",
    response_model=IssueResponse,
    status_code=status.HTTP_201_CREATED,
    summary="Create Issue",
    description="Create a new issue in a project",
    operation_id="create_issue",
)
@limiter.limit(f"{60 * RATE_MULTIPLIER}/minute")
async def create_issue(
    request: Request,
    project_id: UUID,
    issue_in: IssueCreate,
    current_user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
) -> Any:
    """
    Create a new issue within a project.

    The user must own the project or be a superuser.
    The project_id in the path takes precedence over any project_id in the body.

    Args:
        request: FastAPI request object (for rate limiting)
        project_id: UUID of the project to create the issue in
        issue_in: Issue creation data
        current_user: Authenticated user
        db: Database session

    Returns:
        Created issue with full details

    Raises:
        NotFoundError: If project not found
        AuthorizationError: If user lacks access
        ValidationException: If assigned agent not in project
    """
    # Verify project access
    await verify_project_ownership(db, project_id, current_user)

    # Override project_id from path
    issue_in.project_id = project_id

    # Validate assigned agent if provided
    if issue_in.assigned_agent_id:
        agent = await agent_instance_crud.get(db, id=issue_in.assigned_agent_id)
        if not agent:
            raise NotFoundError(
                message=f"Agent instance {issue_in.assigned_agent_id} not found",
                error_code=ErrorCode.NOT_FOUND,
            )
        if agent.project_id != project_id:
            raise ValidationException(
                message="Agent instance does not belong to this project",
                error_code=ErrorCode.VALIDATION_ERROR,
                field="assigned_agent_id",
            )

    # Validate sprint if provided (IDOR prevention)
    if issue_in.sprint_id:
        sprint = await sprint_crud.get(db, id=issue_in.sprint_id)
        if not sprint:
            raise NotFoundError(
                message=f"Sprint {issue_in.sprint_id} not found",
                error_code=ErrorCode.NOT_FOUND,
            )
        if sprint.project_id != project_id:
            raise ValidationException(
                message="Sprint does not belong to this project",
                error_code=ErrorCode.VALIDATION_ERROR,
                field="sprint_id",
            )

    try:
        issue = await issue_crud.create(db, obj_in=issue_in)
        logger.info(
            f"User {current_user.email} created issue '{issue.title}' "
            f"in project {project_id}"
        )

        # Get project details for response
        project = await project_crud.get(db, id=project_id)

        return _build_issue_response(
            issue,
            project_name=project.name if project else None,
            project_slug=project.slug if project else None,
        )

    except ValueError as e:
        logger.warning(f"Failed to create issue: {e!s}")
        raise ValidationException(
            message=str(e),
            error_code=ErrorCode.VALIDATION_ERROR,
        )
    except Exception as e:
        logger.error(f"Error creating issue: {e!s}", exc_info=True)
        raise


@router.get(
    "/projects/{project_id}/issues",
    response_model=PaginatedResponse[IssueResponse],
    summary="List Issues",
    description="Get paginated list of issues in a project with filtering",
    operation_id="list_issues",
)
@limiter.limit(f"{120 * RATE_MULTIPLIER}/minute")
async def list_issues(
    request: Request,
    project_id: UUID,
    pagination: PaginationParams = Depends(),
    status_filter: IssueStatus | None = Query(
        None, alias="status", description="Filter by issue status"
    ),
    priority: IssuePriority | None = Query(None, description="Filter by priority"),
    labels: list[str] | None = Query(
        None, description="Filter by labels (comma-separated)"
    ),
    sprint_id: UUID | None = Query(None, description="Filter by sprint ID"),
    assigned_agent_id: UUID | None = Query(
        None, description="Filter by assigned agent ID"
    ),
    sync_status: SyncStatus | None = Query(
        None, description="Filter by sync status"
    ),
    search: str | None = Query(
        None, min_length=1, max_length=100, description="Search in title and body"
    ),
    sort_by: str = Query(
        "created_at",
        description="Field to sort by (created_at, updated_at, priority, status, title)",
    ),
    sort_order: SortOrder = Query(SortOrder.DESC, description="Sort order"),
    current_user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
) -> Any:
    """
    List issues in a project with comprehensive filtering options.

    Supports filtering by:
    - status: Issue status (open, in_progress, in_review, blocked, closed)
    - priority: Issue priority (low, medium, high, critical)
    - labels: Match issues containing any of the provided labels
    - sprint_id: Issues in a specific sprint
    - assigned_agent_id: Issues assigned to a specific agent
    - sync_status: External tracker sync status
    - search: Full-text search in title and body

    Args:
        request: FastAPI request object
        project_id: Project UUID
        pagination: Pagination parameters
        status_filter: Optional status filter
        priority: Optional priority filter
        labels: Optional labels filter
        sprint_id: Optional sprint filter
        assigned_agent_id: Optional agent assignment filter
        sync_status: Optional sync status filter
        search: Optional search query
        sort_by: Field to sort by
        sort_order: Sort direction
        current_user: Authenticated user
        db: Database session

    Returns:
        Paginated list of issues matching filters
    """
    # Verify project access
    await verify_project_ownership(db, project_id, current_user)

    try:
        # Get filtered issues
        issues, total = await issue_crud.get_by_project(
            db,
            project_id=project_id,
            status=status_filter,
            priority=priority,
            sprint_id=sprint_id,
            assigned_agent_id=assigned_agent_id,
            labels=labels,
            search=search,
            skip=pagination.offset,
            limit=pagination.limit,
            sort_by=sort_by,
            sort_order=sort_order.value,
        )

        # Build response objects
        issue_responses = [_build_issue_response(issue) for issue in issues]

        pagination_meta = create_pagination_meta(
            total=total,
            page=pagination.page,
            limit=pagination.limit,
            items_count=len(issue_responses),
        )

        return PaginatedResponse(data=issue_responses, pagination=pagination_meta)

    except Exception as e:
        logger.error(
            f"Error listing issues for project {project_id}: {e!s}", exc_info=True
        )
        raise


@router.get(
    "/projects/{project_id}/issues/{issue_id}",
    response_model=IssueResponse,
    summary="Get Issue",
    description="Get detailed information about a specific issue",
    operation_id="get_issue",
)
@limiter.limit(f"{120 * RATE_MULTIPLIER}/minute")
async def get_issue(
    request: Request,
    project_id: UUID,
    issue_id: UUID,
    current_user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
) -> Any:
    """
    Get detailed information about a specific issue.

    Returns the issue with expanded relationship data including
    project name, sprint name, and assigned agent type name.

    Args:
        request: FastAPI request object
        project_id: Project UUID
        issue_id: Issue UUID
        current_user: Authenticated user
        db: Database session

    Returns:
        Issue details with relationship data

    Raises:
        NotFoundError: If project or issue not found
        AuthorizationError: If user lacks access
    """
    # Verify project access
    await verify_project_ownership(db, project_id, current_user)

    # Get issue with details
    issue_data = await issue_crud.get_with_details(db, issue_id=issue_id)

    if not issue_data:
        raise NotFoundError(
            message=f"Issue {issue_id} not found",
            error_code=ErrorCode.NOT_FOUND,
        )

    issue = issue_data["issue"]

    # Verify issue belongs to the project
    if issue.project_id != project_id:
        raise NotFoundError(
            message=f"Issue {issue_id} not found in project {project_id}",
            error_code=ErrorCode.NOT_FOUND,
        )

    return _build_issue_response(
        issue,
        project_name=issue_data.get("project_name"),
        project_slug=issue_data.get("project_slug"),
        sprint_name=issue_data.get("sprint_name"),
        assigned_agent_type_name=issue_data.get("assigned_agent_type_name"),
    )


@router.patch(
    "/projects/{project_id}/issues/{issue_id}",
    response_model=IssueResponse,
    summary="Update Issue",
    description="Update an existing issue",
    operation_id="update_issue",
)
@limiter.limit(f"{60 * RATE_MULTIPLIER}/minute")
async def update_issue(
    request: Request,
    project_id: UUID,
    issue_id: UUID,
    issue_in: IssueUpdate,
    current_user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
) -> Any:
    """
    Update an existing issue.

    All fields are optional - only provided fields will be updated.
    Validates that assigned agent belongs to the same project.

    Args:
        request: FastAPI request object
        project_id: Project UUID
        issue_id: Issue UUID
        issue_in: Fields to update
        current_user: Authenticated user
        db: Database session

    Returns:
        Updated issue details

    Raises:
        NotFoundError: If project or issue not found
        AuthorizationError: If user lacks access
        ValidationException: If validation fails
    """
    # Verify project access
    await verify_project_ownership(db, project_id, current_user)

    # Get existing issue
    issue = await issue_crud.get(db, id=issue_id)
    if not issue:
        raise NotFoundError(
            message=f"Issue {issue_id} not found",
            error_code=ErrorCode.NOT_FOUND,
        )

    # Verify issue belongs to the project
    if issue.project_id != project_id:
        raise NotFoundError(
            message=f"Issue {issue_id} not found in project {project_id}",
            error_code=ErrorCode.NOT_FOUND,
        )

    # Validate assigned agent if being updated
    if issue_in.assigned_agent_id is not None:
        agent = await agent_instance_crud.get(db, id=issue_in.assigned_agent_id)
        if not agent:
            raise NotFoundError(
                message=f"Agent instance {issue_in.assigned_agent_id} not found",
                error_code=ErrorCode.NOT_FOUND,
            )
        if agent.project_id != project_id:
            raise ValidationException(
                message="Agent instance does not belong to this project",
                error_code=ErrorCode.VALIDATION_ERROR,
                field="assigned_agent_id",
            )

    # Validate sprint if being updated (IDOR prevention)
    if issue_in.sprint_id is not None:
        sprint = await sprint_crud.get(db, id=issue_in.sprint_id)
        if not sprint:
            raise NotFoundError(
                message=f"Sprint {issue_in.sprint_id} not found",
                error_code=ErrorCode.NOT_FOUND,
            )
        if sprint.project_id != project_id:
            raise ValidationException(
                message="Sprint does not belong to this project",
                error_code=ErrorCode.VALIDATION_ERROR,
                field="sprint_id",
            )

    try:
        updated_issue = await issue_crud.update(db, db_obj=issue, obj_in=issue_in)
        logger.info(
            f"User {current_user.email} updated issue {issue_id} in project {project_id}"
        )

        # Get full details for response
        issue_data = await issue_crud.get_with_details(db, issue_id=issue_id)

        return _build_issue_response(
            updated_issue,
            project_name=issue_data.get("project_name") if issue_data else None,
            project_slug=issue_data.get("project_slug") if issue_data else None,
            sprint_name=issue_data.get("sprint_name") if issue_data else None,
            assigned_agent_type_name=issue_data.get("assigned_agent_type_name")
            if issue_data
            else None,
        )

    except ValueError as e:
        logger.warning(f"Failed to update issue {issue_id}: {e!s}")
        raise ValidationException(
            message=str(e),
            error_code=ErrorCode.VALIDATION_ERROR,
        )
    except Exception as e:
        logger.error(f"Error updating issue {issue_id}: {e!s}", exc_info=True)
        raise


@router.delete(
    "/projects/{project_id}/issues/{issue_id}",
    response_model=MessageResponse,
    summary="Delete Issue",
    description="Soft delete an issue",
    operation_id="delete_issue",
)
@limiter.limit(f"{30 * RATE_MULTIPLIER}/minute")
async def delete_issue(
    request: Request,
    project_id: UUID,
    issue_id: UUID,
    current_user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
) -> Any:
    """
    Soft delete an issue.

    The issue will be marked as deleted but retained in the database.
    This preserves historical data and allows potential recovery.

    Args:
        request: FastAPI request object
        project_id: Project UUID
        issue_id: Issue UUID
        current_user: Authenticated user
        db: Database session

    Returns:
        Success message

    Raises:
        NotFoundError: If project or issue not found
        AuthorizationError: If user lacks access
    """
    # Verify project access
    await verify_project_ownership(db, project_id, current_user)

    # Get existing issue
    issue = await issue_crud.get(db, id=issue_id)
    if not issue:
        raise NotFoundError(
            message=f"Issue {issue_id} not found",
            error_code=ErrorCode.NOT_FOUND,
        )

    # Verify issue belongs to the project
    if issue.project_id != project_id:
        raise NotFoundError(
            message=f"Issue {issue_id} not found in project {project_id}",
            error_code=ErrorCode.NOT_FOUND,
        )

    try:
        await issue_crud.soft_delete(db, id=issue_id)
        logger.info(
            f"User {current_user.email} deleted issue {issue_id} "
            f"('{issue.title}') from project {project_id}"
        )

        return MessageResponse(
            success=True,
            message=f"Issue '{issue.title}' has been deleted",
        )

    except Exception as e:
        logger.error(f"Error deleting issue {issue_id}: {e!s}", exc_info=True)
        raise


# ===== Issue Assignment Endpoint =====


@router.post(
    "/projects/{project_id}/issues/{issue_id}/assign",
    response_model=IssueResponse,
    summary="Assign Issue",
    description="Assign an issue to an agent or human",
    operation_id="assign_issue",
)
@limiter.limit(f"{60 * RATE_MULTIPLIER}/minute")
async def assign_issue(
    request: Request,
    project_id: UUID,
    issue_id: UUID,
    assignment: IssueAssign,
    current_user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
) -> Any:
    """
    Assign an issue to an agent or human.

    Only one type of assignment is allowed at a time:
    - assigned_agent_id: Assign to an AI agent instance
    - human_assignee: Assign to a human (name/email string)

    To unassign, pass both as null/None.

    Args:
        request: FastAPI request object
        project_id: Project UUID
        issue_id: Issue UUID
        assignment: Assignment data
        current_user: Authenticated user
        db: Database session

    Returns:
        Updated issue with assignment

    Raises:
        NotFoundError: If project, issue, or agent not found
        AuthorizationError: If user lacks access
        ValidationException: If agent not in project
    """
    # Verify project access
    await verify_project_ownership(db, project_id, current_user)

    # Get existing issue
    issue = await issue_crud.get(db, id=issue_id)
    if not issue:
        raise NotFoundError(
            message=f"Issue {issue_id} not found",
            error_code=ErrorCode.NOT_FOUND,
        )

    # Verify issue belongs to the project
    if issue.project_id != project_id:
        raise NotFoundError(
            message=f"Issue {issue_id} not found in project {project_id}",
            error_code=ErrorCode.NOT_FOUND,
        )

    # Process assignment based on type
    if assignment.assigned_agent_id:
        # Validate agent exists and belongs to project
        agent = await agent_instance_crud.get(db, id=assignment.assigned_agent_id)
        if not agent:
            raise NotFoundError(
                message=f"Agent instance {assignment.assigned_agent_id} not found",
                error_code=ErrorCode.NOT_FOUND,
            )
        if agent.project_id != project_id:
            raise ValidationException(
                message="Agent instance does not belong to this project",
                error_code=ErrorCode.VALIDATION_ERROR,
                field="assigned_agent_id",
            )

        updated_issue = await issue_crud.assign_to_agent(
            db, issue_id=issue_id, agent_id=assignment.assigned_agent_id
        )
        logger.info(
            f"User {current_user.email} assigned issue {issue_id} to agent {agent.name}"
        )

    elif assignment.human_assignee:
        updated_issue = await issue_crud.assign_to_human(
            db, issue_id=issue_id, human_assignee=assignment.human_assignee
        )
        logger.info(
            f"User {current_user.email} assigned issue {issue_id} "
            f"to human '{assignment.human_assignee}'"
        )

    else:
        # Unassign - clear both agent and human
        updated_issue = await issue_crud.assign_to_agent(
            db, issue_id=issue_id, agent_id=None
        )
        logger.info(
            f"User {current_user.email} unassigned issue {issue_id}"
        )

    if not updated_issue:
        raise NotFoundError(
            message=f"Issue {issue_id} not found",
            error_code=ErrorCode.NOT_FOUND,
        )

    # Get full details for response
    issue_data = await issue_crud.get_with_details(db, issue_id=issue_id)

    return _build_issue_response(
        updated_issue,
        project_name=issue_data.get("project_name") if issue_data else None,
        project_slug=issue_data.get("project_slug") if issue_data else None,
        sprint_name=issue_data.get("sprint_name") if issue_data else None,
        assigned_agent_type_name=issue_data.get("assigned_agent_type_name")
        if issue_data
        else None,
    )


@router.delete(
    "/projects/{project_id}/issues/{issue_id}/assignment",
    response_model=IssueResponse,
    summary="Unassign Issue",
    description="""
    Remove agent/human assignment from an issue.

    **Authentication**: Required (Bearer token)
    **Authorization**: Project owner or superuser

    This clears both agent and human assignee fields.

    **Rate Limit**: 60 requests/minute
    """,
    operation_id="unassign_issue",
)
@limiter.limit(f"{60 * RATE_MULTIPLIER}/minute")
async def unassign_issue(
    request: Request,
    project_id: UUID,
    issue_id: UUID,
    current_user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
) -> Any:
    """
    Remove assignment from an issue.

    Clears both assigned_agent_id and human_assignee fields.
    """
    # Verify project access
    await verify_project_ownership(db, project_id, current_user)

    # Get existing issue
    issue = await issue_crud.get(db, id=issue_id)
    if not issue:
        raise NotFoundError(
            message=f"Issue {issue_id} not found",
            error_code=ErrorCode.NOT_FOUND,
        )

    # Verify issue belongs to project (IDOR prevention)
    if issue.project_id != project_id:
        raise NotFoundError(
            message=f"Issue {issue_id} not found in project {project_id}",
            error_code=ErrorCode.NOT_FOUND,
        )

    # Unassign the issue
    updated_issue = await issue_crud.unassign(db, issue_id=issue_id)

    if not updated_issue:
        raise NotFoundError(
            message=f"Issue {issue_id} not found",
            error_code=ErrorCode.NOT_FOUND,
        )

    logger.info(f"User {current_user.email} unassigned issue {issue_id}")

    # Get full details for response
    issue_data = await issue_crud.get_with_details(db, issue_id=issue_id)

    return _build_issue_response(
        updated_issue,
        project_name=issue_data.get("project_name") if issue_data else None,
        project_slug=issue_data.get("project_slug") if issue_data else None,
        sprint_name=issue_data.get("sprint_name") if issue_data else None,
        assigned_agent_type_name=issue_data.get("assigned_agent_type_name")
        if issue_data
        else None,
    )


# ===== Issue Sync Endpoint =====


@router.post(
    "/projects/{project_id}/issues/{issue_id}/sync",
    response_model=MessageResponse,
    summary="Trigger Issue Sync",
    description="Trigger synchronization with external issue tracker",
    operation_id="sync_issue",
)
@limiter.limit(f"{30 * RATE_MULTIPLIER}/minute")
async def sync_issue(
    request: Request,
    project_id: UUID,
    issue_id: UUID,
    current_user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
) -> Any:
    """
    Trigger synchronization of an issue with its external tracker.

    This endpoint queues a sync task for the issue. The actual synchronization
    happens asynchronously via Celery.

    Prerequisites:
    - Issue must have external_tracker_type configured
    - Project must have integration settings for the tracker

    Args:
        request: FastAPI request object
        project_id: Project UUID
        issue_id: Issue UUID
        current_user: Authenticated user
        db: Database session

    Returns:
        Message indicating sync has been triggered

    Raises:
        NotFoundError: If project or issue not found
        AuthorizationError: If user lacks access
        ValidationException: If issue has no external tracker
    """
    # Verify project access
    await verify_project_ownership(db, project_id, current_user)

    # Get existing issue
    issue = await issue_crud.get(db, id=issue_id)
    if not issue:
        raise NotFoundError(
            message=f"Issue {issue_id} not found",
            error_code=ErrorCode.NOT_FOUND,
        )

    # Verify issue belongs to the project
    if issue.project_id != project_id:
        raise NotFoundError(
            message=f"Issue {issue_id} not found in project {project_id}",
            error_code=ErrorCode.NOT_FOUND,
        )

    # Check if issue has external tracker configured
    if not issue.external_tracker_type:
        raise ValidationException(
            message="Issue does not have an external tracker configured",
            error_code=ErrorCode.VALIDATION_ERROR,
            field="external_tracker_type",
        )

    # Update sync status to pending
    await issue_crud.update_sync_status(
        db,
        issue_id=issue_id,
        sync_status=SyncStatus.PENDING,
    )

    # TODO: Queue Celery task for actual sync
    # When Celery is set up, this will be:
    # from app.tasks.sync import sync_issue_task
    # sync_issue_task.delay(str(issue_id))

    logger.info(
        f"User {current_user.email} triggered sync for issue {issue_id} "
        f"(tracker: {issue.external_tracker_type})"
    )

    return MessageResponse(
        success=True,
        message=f"Sync triggered for issue '{issue.title}'. "
        f"Status will update when complete.",
    )


# ===== Issue Statistics Endpoint =====


@router.get(
    "/projects/{project_id}/issues/stats",
    response_model=IssueStats,
    summary="Get Issue Statistics",
    description="Get aggregated issue statistics for a project",
    operation_id="get_issue_stats",
)
@limiter.limit(f"{60 * RATE_MULTIPLIER}/minute")
async def get_issue_stats(
    request: Request,
    project_id: UUID,
    current_user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
) -> Any:
    """
    Get aggregated statistics for issues in a project.

    Returns counts by status and priority, along with story point totals.

    Args:
        request: FastAPI request object
        project_id: Project UUID
        current_user: Authenticated user
        db: Database session

    Returns:
        Issue statistics including counts by status/priority and story points

    Raises:
        NotFoundError: If project not found
        AuthorizationError: If user lacks access
    """
    # Verify project access
    await verify_project_ownership(db, project_id, current_user)

    try:
        stats = await issue_crud.get_project_stats(db, project_id=project_id)
        return IssueStats(**stats)

    except Exception as e:
        logger.error(
            f"Error getting issue stats for project {project_id}: {e!s}",
            exc_info=True,
        )
        raise
