"""Agent-scoped Forgejo issue read APIs for board-linked repositories."""

from __future__ import annotations

from typing import TYPE_CHECKING
from uuid import UUID

from fastapi import APIRouter, Depends, HTTPException, Query, status
from sqlmodel import select, func
from sqlalchemy import and_

from app.api.deps import get_board_for_actor_read
from app.core.agent_auth import get_agent_auth_context
from app.db.session import get_session
from app.models.boards import Board
from app.models.board_repository_links import BoardRepositoryLink
from app.models.forgejo_issues import ForgejoIssue
from app.schemas.forgejo_issues import ForgejoIssueRead, ForgejoIssueListResponse, CloseIssueResponse
from app.services.activity_log import record_activity
from app.services.forgejo_issue_close import (
    CloseIssueAccessError,
    CloseIssueNotFoundError,
    CloseIssueRemoteError,
    close_issue_by_id,
)
from app.services.openclaw.policies import OpenClawAuthorizationPolicy

if TYPE_CHECKING:
    from sqlmodel.ext.asyncio.session import AsyncSession
    from app.core.agent_auth import AgentAuthContext


router = APIRouter(prefix="/agent/boards", tags=["agent-board-issues"])
SESSION_DEP = Depends(get_session)
BOARD_READ_DEP = Depends(get_board_for_actor_read)
AGENT_CTX_DEP = Depends(get_agent_auth_context)


def _agent_board_openapi_hints(
    *,
    intent: str,
    when_to_use: list[str],
    routing_examples: list[dict[str, object]],
    required_actor: str = "any_agent",
    when_not_to_use: list[str] | None = None,
    routing_policy: list[str] | None = None,
) -> dict[str, object]:
    """Generate LLM routing hints for board-scoped agent endpoints."""
    return {
        "x-llm-intent": intent,
        "x-when-to-use": when_to_use,
        "x-when-not-to-use": when_not_to_use
        or [
            "Use a more specific endpoint for direct state mutation or direct messaging.",
        ],
        "x-required-actor": required_actor,
        "x-prerequisites": [
            "Authenticated agent token",
            "Board access is validated before execution",
        ],
        "x-side-effects": [
            "Read-only access to issues linked to board repositories",
        ],
        "x-negative-guidance": [
            "Avoid this endpoint when a focused sibling endpoint handles the action.",
        ],
        "x-routing-policy": routing_policy
        or [
            "Use when the request intent matches this board-scoped route.",
            "Prefer dedicated mutation/read routes once intent is narrowed.",
        ],
        "x-routing-policy-examples": routing_examples,
    }


@router.get(
    "/{board_id}/git/issues",
    response_model=ForgejoIssueListResponse,
    summary="List issues for board's linked repositories",
    description=(
        "List Forgejo issues from repositories linked to the specified board.\n\n"
        "Use this endpoint when an agent needs to discover issues across all "
        "repositories associated with its assigned board.\n\n"
        "LLM Routing Guidance:\n"
        "- Use when you need to list issues for a specific board's repositories\n"
        "- Filter by state, search text, or assignee for targeted results\n"
        "- Use this instead of generic issue lists when board context matters\n"
        "- Exclude pull requests automatically"
    ),
    openapi_extra=_agent_board_openapi_hints(
        intent="board_issue_discovery",
        when_to_use=[
            "Need to list issues for a specific board's repositories",
            "Looking for issues to assign or track",
            "Discovering work items related to a board's scope",
        ],
        when_not_to_use=[
            "Listing all issues across all repositories (use forgejo issues list)",
            "Working with issues from unlinked repositories",
            "Need repository-agnostic issue search",
        ],
        routing_examples=[
            {
                "input": {
                    "intent": "list issues for board xyz",
                    "board_id": "uuid",
                },
                "decision": "agent_board_list_issues",
            },
            {
                "input": {
                    "intent": "find issues assigned to me",
                    "assignee": "me",
                },
                "decision": "agent_board_list_issues (with assignee filter)",
            },
        ],
    ),
)
async def list_board_issues(
    board_id: UUID,
    session: AsyncSession = SESSION_DEP,
    board: ForgejoIssue = BOARD_READ_DEP,
    state: str | None = Query(default=None, description="Filter by issue state (open/closed)"),
    search: str | None = Query(default=None, description="Search in title/body"),
    page: int = Query(default=1, ge=1, description="Page number"),
    limit: int = Query(default=30, ge=1, le=100, description="Items per page"),
) -> ForgejoIssueListResponse:
    """List issues for repositories linked to a board."""
    # Get linked repositories
    link_statement = select(BoardRepositoryLink).where(
        BoardRepositoryLink.board_id == board_id
    )
    links = (await session.exec(link_statement)).all()
    
    if not links:
        return ForgejoIssueListResponse(
            items=[],
            total=0,
            page=page,
            limit=limit,
        )

    repository_ids = [link.repository_id for link in links]
    
    # Build base query
    statement = select(ForgejoIssue).where(
        ForgejoIssue.repository_id.in_(repository_ids)
    )
    
    # Apply filters
    if state:
        statement = statement.where(ForgejoIssue.state == state)
    if search:
        statement = statement.where(
            ForgejoIssue.title.ilike(f"%{search}%") |
            ForgejoIssue.body_preview.ilike(f"%{search}%")
        )
    
    # Count total
    count_statement = select(func.count(ForgejoIssue.id)).where(
        ForgejoIssue.repository_id.in_(repository_ids)
    )
    if state:
        count_statement = count_statement.where(ForgejoIssue.state == state)
    if search:
        count_statement = count_statement.where(
            ForgejoIssue.title.ilike(f"%{search}%") |
            ForgejoIssue.body_preview.ilike(f"%{search}%")
        )
    
    total = await session.scalar(count_statement) or 0
    
    # Apply pagination and execute
    offset = (page - 1) * limit
    statement = statement.offset(offset).limit(limit)
    issues = (await session.exec(statement)).all()
    
    return ForgejoIssueListResponse(
        items=[ForgejoIssueRead.model_validate(issue) for issue in issues],
        total=total,
        page=page,
        limit=limit,
    )


@router.get(
    "/{board_id}/git/issues/{issue_id}",
    response_model=ForgejoIssueRead,
    summary="Read one issue from board-linked repositories",
    description=(
        "Read a specific Forgejo issue by id, ensuring it belongs to a repository "
        "linked to the specified board.\n\n"
        "Use this endpoint when an agent needs to inspect a specific issue "
        "within the context of a board's repositories."
    ),
    openapi_extra=_agent_board_openapi_hints(
        intent="board_issue_inspection",
        when_to_use=[
            "Need to read a specific issue's details",
            "Verifying issue state before taking action",
            "Inspecting issue metadata for board context",
        ],
        when_not_to_use=[
            "Listing multiple issues (use list endpoint)",
            "Working with issues from unlinked repositories",
        ],
        routing_examples=[
            {
                "input": {
                    "intent": "read issue 123 for board xyz",
                    "board_id": "uuid",
                    "issue_id": "uuid",
                },
                "decision": "agent_board_read_issue",
            },
        ],
    ),
)
async def read_board_issue(
    board_id: UUID,
    issue_id: UUID,
    session: AsyncSession = SESSION_DEP,
    board: ForgejoIssue = BOARD_READ_DEP,
) -> ForgejoIssueRead:
    """Read one issue from board-linked repositories."""
    # Get linked repositories
    link_statement = select(BoardRepositoryLink).where(
        BoardRepositoryLink.board_id == board_id
    )
    links = (await session.exec(link_statement)).all()
    
    if not links:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="No repositories linked to this board",
        )

    repository_ids = [link.repository_id for link in links]
    
    # Find issue
    statement = select(ForgejoIssue).where(
        and_(
            ForgejoIssue.id == issue_id,
            ForgejoIssue.repository_id.in_(repository_ids),
        )
    )
    issue = (await session.exec(statement)).first()
    
    if issue is None:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Issue not found or not linked to this board",
        )
    
    return ForgejoIssueRead.model_validate(issue)


@router.post(
    "/{board_id}/git/issues/{issue_id}/close",
    response_model=CloseIssueResponse,
    summary="Close a Forgejo issue (agent)",
    description=(
        "Close a Forgejo issue by its local ID as a board-lead agent. "
        "Only board lead agents can close issues. The issue must belong to a "
        "repository linked to the target board."
    ),
    openapi_extra=_agent_board_openapi_hints(
        intent="agent_board_close_issue",
        when_to_use=[
            "Lead agent needs to close a specific issue",
            "Finalizing work items after completion",
            "Closing issues after deployment or release",
        ],
        when_not_to_use=[
            "Worker agents closing issues (use human endpoint or ask lead)",
            "Working with issues from unlinked repositories",
        ],
        required_actor="board_lead",
        routing_examples=[
            {
                "input": {
                    "intent": "close issue 123 for board xyz",
                    "board_id": "uuid",
                    "issue_id": "uuid",
                },
                "decision": "agent_board_close_issue",
            },
        ],
    ),
    responses={
        status.HTTP_200_OK: {
            "description": "Issue closed successfully",
            "content": {
                "application/json": {
                    "example": {
                        "success": True,
                        "issue_id": "123e4567-e89b-12d3-a456-426614174000",
                        "forgejo_issue_number": 42,
                        "state": "closed",
                        "forgejo_closed_at": "2026-05-19T03:43:00+00:00",
                        "last_synced_at": "2026-05-19T03:43:00+00:00",
                    }
                }
            },
        },
        status.HTTP_404_NOT_FOUND: {
            "description": "Issue not found or not linked to this board",
        },
        status.HTTP_403_FORBIDDEN: {
            "description": "Caller is not board lead",
        },
        status.HTTP_409_CONFLICT: {
            "description": "Organization mismatch or access denied",
        },
        status.HTTP_502_BAD_GATEWAY: {
            "description": "Forgejo API call failed",
        },
    },
)
async def close_board_issue(
    board_id: UUID,
    issue_id: UUID,
    session: AsyncSession = SESSION_DEP,
    board: Board = BOARD_READ_DEP,
    agent_ctx: AgentAuthContext = AGENT_CTX_DEP,
) -> CloseIssueResponse:
    """Close a Forgejo issue as a board-lead agent.
    
    Only board lead agents can close issues. The issue must belong to a repository
    linked to the target board.
    """
    links = (
        await session.exec(
            select(BoardRepositoryLink).where(
                BoardRepositoryLink.organization_id == board.organization_id,
                BoardRepositoryLink.board_id == board_id,
            )
        )
    ).all()
    repository_ids = [link.repository_id for link in links]
    if not repository_ids:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Issue not found or not linked to this board",
        )

    statement = select(ForgejoIssue).where(
        and_(
            ForgejoIssue.id == issue_id,
            ForgejoIssue.repository_id.in_(repository_ids),
        ),
    )
    issue = (await session.exec(statement)).first()
    
    if issue is None:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Issue not found or not linked to this board",
        )

    # Verify agent is board lead.
    OpenClawAuthorizationPolicy.require_board_lead_actor(
        actor_agent=agent_ctx.agent,
        detail="Only board leads can close issues",
    )

    # Close the issue using the service.
    try:
        result = await close_issue_by_id(
            session=session,
            issue_id=issue_id,
            actor_agent_id=agent_ctx.agent.id,
        )
    except CloseIssueNotFoundError as e:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(e))
    except CloseIssueAccessError as e:
        raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail=str(e))
    except CloseIssueRemoteError as e:
        raise HTTPException(status_code=status.HTTP_502_BAD_GATEWAY, detail=str(e))

    repository_full_name = str(result.get("repository_full_name") or "unknown/unknown")
    record_activity(
        session,
        event_type="forgejo.issue.closed",
        message=(
            "Forgejo issue closed by agent "
            f"{agent_ctx.agent.id}: {repository_full_name}#{result['forgejo_issue_number']}"
        ),
        board_id=board_id,
        agent_id=agent_ctx.agent.id,
    )
    await session.commit()

    return CloseIssueResponse(
        success=result["success"],
        issue_id=result["issue_id"],
        forgejo_issue_number=result["forgejo_issue_number"],
        state=result["state"],
        forgejo_closed_at=result.get("forgejo_closed_at"),
        last_synced_at=result.get("last_synced_at") or "",
    )
