"""Feature REST API routes."""

import re
import asyncpg
from fastapi import APIRouter, Body, Depends, HTTPException, Request

from peon_mcp.common.auth import AuthInfo, get_auth
from peon_mcp.common.constants import VALID_FEATURE_STATUSES
from peon_mcp.common.dependencies import get_db, get_project_id
from peon_mcp.common.events import FeatureCompletedEvent, event_bus
from peon_mcp.common.schemas import OkResponse, PaginatedResponse
from peon_mcp.common.scoping import authorize_project_access, authorize_entity_project_access
from peon_mcp.db import row_to_dict
from peon_mcp.features.schemas import (
    CompleteFeatureRequest,
    CreateFeatureRequest,
    FeatureResponse,
    FeatureTasksResponse,
    ResolveConflictsResponse,
    UpdateFeatureRequest,
)
from peon_mcp.github import create_feature_pr, merge_main_into_feature
from peon_mcp.tasks.schemas import TaskResponse

router = APIRouter(tags=["Features"])


@router.get("/api/projects/{owner}/{repo}/features", response_model=PaginatedResponse[FeatureResponse])
async def list_features(
    request: Request,
    status: str | None = None,
    limit: int = 50,
    offset: int = 0,
    project_id: str = Depends(get_project_id),
    auth: AuthInfo = Depends(get_auth),
    db=Depends(get_db),
):
    await authorize_project_access(db, auth, project_id, request)
    # Build the WHERE clause for filtering
    where_clause = "f.project_id = ?"
    params: list = [project_id]
    if status:
        # Validate status parameter
        if status not in VALID_FEATURE_STATUSES:
            raise HTTPException(
                400,
                detail=f"Invalid status. Must be one of: {', '.join(sorted(VALID_FEATURE_STATUSES))}"
            )
        where_clause += " AND f.status = ?"
        params.append(status)

    # Get total count for pagination metadata
    count_query = f"SELECT COUNT(*) FROM features f WHERE {where_clause}"
    count_rows = await db.execute_fetchall(count_query, params)
    total = count_rows[0]["count"] if count_rows else 0

    # Single query with LEFT JOIN to get features and task counts
    query = f"""
        SELECT f.id, f.project_id, f.name, f.description, f.status, f.branch,
               f.pr_url, f.created_at, f.updated_at,
               SUM(CASE WHEN t.status = 'grooming' THEN 1 ELSE 0 END) as grooming_count,
               SUM(CASE WHEN t.status = 'todo' THEN 1 ELSE 0 END) as todo_count,
               SUM(CASE WHEN t.status = 'in_progress' THEN 1 ELSE 0 END) as in_progress_count,
               SUM(CASE WHEN t.status = 'done' THEN 1 ELSE 0 END) as done_count,
               SUM(CASE WHEN t.status = 'cancelled' THEN 1 ELSE 0 END) as cancelled_count
        FROM features f
        LEFT JOIN tasks t ON t.feature_id = f.id
        WHERE {where_clause}
        GROUP BY f.id
        ORDER BY f.created_at DESC
        LIMIT ? OFFSET ?
    """

    params.extend([limit, offset])

    rows = await db.execute_fetchall(query, params)
    items = []
    for row in rows:
        row_dict = row_to_dict(row)
        # Extract and remove the count columns from the feature dict
        task_counts = {}
        for status_key in ["grooming", "todo", "in_progress", "done", "cancelled", "merged", "timeout", "waiting_for_input"]:
            count_key = f"{status_key}_count"
            if count_key in row_dict:
                count = row_dict.pop(count_key)
                if count > 0:
                    task_counts[status_key] = count
        row_dict["task_counts"] = task_counts
        items.append(row_dict)

    return {
        "items": items,
        "total": total,
        "limit": limit,
        "offset": offset,
        "has_next": (offset + len(items)) < total,
    }


@router.post("/api/projects/{owner}/{repo}/features", response_model=FeatureResponse, status_code=201)
async def create_feature(
    body: CreateFeatureRequest,
    request: Request,
    project_id: str = Depends(get_project_id),
    auth: AuthInfo = Depends(get_auth),
    db=Depends(get_db),
):
    await authorize_project_access(db, auth, project_id, request)
    name = body.name.strip()
    if not name:
        raise HTTPException(400, detail="name is required")
    description = body.description
    status = body.status
    branch = body.branch.strip()

    # Auto-generate branch name if not provided
    if not branch:
        slug = re.sub(r'[^a-z0-9]+', '-', name.lower().strip()).strip('-')[:50]
        branch = f"feature/{slug}"

    try:
        rows = await db.execute_fetchall(
            "INSERT INTO features (project_id, name, description, status, branch) VALUES (?, ?, ?, ?, ?) RETURNING id",
            (project_id, name, description, status, branch),
        )
        await db.commit()
        new_id = rows[0]["id"]
        rows = await db.execute_fetchall(
            "SELECT * FROM features WHERE id = ?", (new_id,)
        )
        return row_to_dict(rows[0])
    except asyncpg.UniqueViolationError as e:
        # Check if this is a duplicate branch constraint violation
        error_msg = str(e)
        if "idx_features_project_branch" in error_msg or "features.project_id, features.branch" in error_msg:
            raise HTTPException(
                409,
                detail=f"A feature with branch '{branch}' already exists in this project"
            )
        # Re-raise other integrity errors (e.g., foreign key violations)
        raise


@router.get("/api/features/{feature_id}", response_model=FeatureResponse)
async def get_feature(
    feature_id: int,
    request: Request,
    auth: AuthInfo = Depends(get_auth),
    db=Depends(get_db),
):
    entity = await authorize_entity_project_access(
        db, auth, request, "features", feature_id,
    )
    return entity


@router.put("/api/features/{feature_id}", response_model=FeatureResponse)
async def update_feature(
    feature_id: int,
    body: UpdateFeatureRequest,
    request: Request,
    auth: AuthInfo = Depends(get_auth),
    db=Depends(get_db),
):
    await authorize_entity_project_access(db, auth, request, "features", feature_id)
    provided = body.model_dump(exclude_unset=True)

    # Validate status if provided
    if "status" in provided:
        if provided["status"] not in VALID_FEATURE_STATUSES:
            raise HTTPException(
                400,
                detail=f"Invalid status. Must be one of: {', '.join(sorted(VALID_FEATURE_STATUSES))}"
            )

    fields: list[str] = []
    params: list = []
    for key in ("name", "description", "status", "branch", "pr_url"):
        if key in provided:
            fields.append(f"{key} = ?")
            params.append(provided[key])
    if not fields:
        raise HTTPException(400, detail="No fields to update")
    fields.append("updated_at = CURRENT_TIMESTAMP")
    params.append(feature_id)
    await db.execute(
        f"UPDATE features SET {', '.join(fields)} WHERE id = ?", params
    )
    await db.commit()
    rows = await db.execute_fetchall(
        "SELECT * FROM features WHERE id = ?", (feature_id,)
    )
    if not rows:
        raise HTTPException(404, detail="Feature not found")
    return row_to_dict(rows[0])


@router.delete("/api/features/{feature_id}", response_model=OkResponse)
async def delete_feature(
    feature_id: int,
    request: Request,
    auth: AuthInfo = Depends(get_auth),
    db=Depends(get_db),
):
    await authorize_entity_project_access(db, auth, request, "features", feature_id)
    # Check if any tasks reference this feature
    task_rows = await db.execute_fetchall(
        "SELECT id, title FROM tasks WHERE feature_id = ?", (feature_id,)
    )
    if task_rows:
        task_ids = [str(r["id"]) for r in task_rows]
        raise HTTPException(
            409,
            detail=f"Cannot delete feature: {len(task_rows)} task(s) still reference it (IDs: {', '.join(task_ids)}). Delete or reassign these tasks first."
        )

    cursor = await db.execute(
        "DELETE FROM features WHERE id = ?", (feature_id,)
    )
    await db.commit()
    if cursor.rowcount == 0:
        raise HTTPException(404, detail="Feature not found")
    return {"ok": True}


@router.post("/api/features/{feature_id}/complete", response_model=FeatureResponse)
async def complete_feature(
    feature_id: int,
    request: Request,
    body: CompleteFeatureRequest | None = Body(default=None),
    auth: AuthInfo = Depends(get_auth),
    db=Depends(get_db),
):
    await authorize_entity_project_access(db, auth, request, "features", feature_id)
    """Mark a feature as done after all its tasks are complete."""
    pr_url = (body.pr_url if body else "").strip()

    # Check if feature exists and get project_id
    feat_rows = await db.execute_fetchall(
        "SELECT * FROM features WHERE id = ?", (feature_id,)
    )
    if not feat_rows:
        raise HTTPException(404, detail="Feature not found")

    feature = row_to_dict(feat_rows[0])

    # Check that all tasks are done or cancelled
    task_rows = await db.execute_fetchall(
        "SELECT id, status FROM tasks WHERE feature_id = ?", (feature_id,)
    )
    if task_rows:
        incomplete_tasks = [
            row["id"] for row in task_rows if row["status"] not in ("done", "cancelled", "merged")
        ]
        if incomplete_tasks:
            raise HTTPException(400, detail=(
                f"Cannot complete feature {feature_id} - the following tasks are incomplete: "
                f"{', '.join(f'#{task_id}' for task_id in incomplete_tasks)}. "
                f"Please complete or cancel these tasks first."
            ))

    # Look up project path (needed for PR creation and auto-merge)
    project_rows = await db.execute_fetchall(
        "SELECT path FROM projects WHERE id = ?", (feature["project_id"],)
    )
    project_path = project_rows[0]["path"] if project_rows else None

    # If pr_url not provided, auto-create via gh CLI
    if not pr_url:
        if not project_path:
            raise HTTPException(404, detail=f"Project '{feature['project_id']}' not found")

        # Try to create PR via gh CLI using shared function
        try:
            pr_url = await create_feature_pr(feature, project_path)
        except ValueError as e:
            raise HTTPException(400, detail=str(e))
        except FileNotFoundError as e:
            raise HTTPException(500, detail=str(e))
        except RuntimeError as e:
            raise HTTPException(500, detail=str(e))

    # Mark feature as done and store PR URL
    await db.execute(
        "UPDATE features SET status = 'done', pr_url = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?",
        (pr_url, feature_id),
    )
    await db.commit()

    rows = await db.execute_fetchall(
        "SELECT * FROM features WHERE id = ?", (feature_id,)
    )
    completed_feature = row_to_dict(rows[0])
    event_bus.emit(FeatureCompletedEvent(
        project_id=feature["project_id"],
        feature_id=feature_id,
        feature_name=completed_feature.get("name", ""),
        branch=completed_feature.get("branch", ""),
        pr_url=pr_url or "",
        project_path=project_path or "",
        feature_data=completed_feature,
    ))

    return completed_feature


@router.get("/api/features/{feature_id}/tasks", response_model=FeatureTasksResponse)
async def list_feature_tasks(
    feature_id: int,
    request: Request,
    auth: AuthInfo = Depends(get_auth),
    db=Depends(get_db),
):
    await authorize_entity_project_access(db, auth, request, "features", feature_id)
    """List all tasks for a specific feature."""
    rows = await db.execute_fetchall(
        "SELECT * FROM tasks WHERE feature_id = ? ORDER BY created_at DESC",
        (feature_id,)
    )
    items = [row_to_dict(r) for r in rows]
    return {"items": items, "total": len(items)}


@router.post("/api/features/{feature_id}/resolve-conflicts", response_model=ResolveConflictsResponse)
async def resolve_feature_conflicts(
    feature_id: int,
    request: Request,
    auth: AuthInfo = Depends(get_auth),
    db=Depends(get_db),
):
    await authorize_entity_project_access(db, auth, request, "features", feature_id)
    """Merge origin/main into the feature branch to resolve PR conflicts.

    Creates a temporary worktree, merges origin/main into the feature branch,
    and pushes the result. Returns conflict details if the merge cannot be
    completed automatically.
    """
    feat_rows = await db.execute_fetchall(
        "SELECT * FROM features WHERE id = ?", (feature_id,)
    )
    if not feat_rows:
        raise HTTPException(404, detail="Feature not found")

    feature = row_to_dict(feat_rows[0])

    project_rows = await db.execute_fetchall(
        "SELECT path FROM projects WHERE id = ?", (feature["project_id"],)
    )
    if not project_rows:
        raise HTTPException(404, detail=f"Project '{feature['project_id']}' not found")

    project_path = project_rows[0]["path"]

    try:
        result = await merge_main_into_feature(feature, project_path)
    except ValueError as e:
        raise HTTPException(400, detail=str(e))
    except RuntimeError as e:
        raise HTTPException(500, detail=str(e))

    return result
