"""Tool instance API endpoints."""

import logging
import uuid

import httpx
from fastapi import (
    APIRouter,
    Depends,
    HTTPException,
    Request,
    Response,
    status,
)
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from src.auth.dependencies import (
    _get_owned_project,
    _get_user,
    get_current_user_id,
    get_db_session,
)
from src.models import ToolInstance
from src.services.docker import get_container_logs, get_container_status
from src.services.shared.tunnel import check_tunnel_health
from src.schemas.tool import CreateInstanceRequest
from src.services.tool.instance_service import (
    create_tool_instance,
    recreate_instance_tunnel,
    rename_tool_instance,
)

logger = logging.getLogger(__name__)

router = APIRouter(prefix="/projects", tags=["tool-instances"])


@router.post(
    "/{project_id}/repositories/{repo_id}/instances",
    summary="Create tool instance",
    description="Create a new tool instance for a repository.",
)
async def create_instance(
    project_id: uuid.UUID,
    repo_id: uuid.UUID,
    data: CreateInstanceRequest,
    user_id: uuid.UUID = Depends(get_current_user_id),
    session: AsyncSession = Depends(get_db_session),
) -> dict:
    _user = await _get_user(session, user_id)
    _project = await _get_owned_project(project_id, user_id, session)
    try:
        instance = await create_tool_instance(session, user_id, project_id, repo_id, data)
    except ValueError as exc:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(exc))
    except RuntimeError as exc:
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(exc)
        )
    return {
        "id": str(instance.id),
        "name": instance.name,
        "display_name": instance.display_name,
        "tool_type_id": str(instance.tool_type_id),
        "status": instance.status,
        "workspace_id": str(instance.workspace_id) if instance.workspace_id else None,
        "selected_config_profile_id": str(instance.selected_config_profile_id) if instance.selected_config_profile_id else None,
        "created_at": instance.created_at.isoformat(),
    }


@router.get(
    "/{project_id}/repositories/{repo_id}/instances",
    summary="List instances",
    description="List all tool instances for a repository.",
)
async def list_instances(
    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]:
    _user = await _get_user(session, user_id)
    _project = await _get_owned_project(project_id, user_id, session)

    result = await session.execute(
        select(ToolInstance)
        .where(ToolInstance.repository_id == repo_id)
        .where(ToolInstance.owner_id == user_id)
        .order_by(ToolInstance.created_at.desc())
    )
    instances = result.scalars().all()

    return [
        {"id": str(i.id), "name": i.name, "display_name": i.display_name,
         "tool_type_id": str(i.tool_type_id), "status": i.status, "url": i.url,
         "port": i.port, "container_id": i.container_id,
         "created_at": i.created_at.isoformat() if i.created_at else None}
        for i in instances
    ]


@router.get(
    "/{project_id}/repositories/{repo_id}/instances/{instance_id}",
    summary="Get instance",
    description="Get details for a specific tool instance.",
)
async def get_instance(
    project_id: uuid.UUID,
    repo_id: uuid.UUID,
    instance_id: uuid.UUID,
    user_id: uuid.UUID = Depends(get_current_user_id),
    session: AsyncSession = Depends(get_db_session),
) -> dict:
    _user = await _get_user(session, user_id)
    _project = await _get_owned_project(project_id, user_id, session)

    instance = await session.get(ToolInstance, instance_id)
    if instance is None or instance.repository_id != repo_id:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND, detail="instance not found"
        )

    docker_status = None
    if instance.container_id:
        docker_status = get_container_status(instance.container_id)

    return {
        "id": str(instance.id),
        "name": instance.name,
        "display_name": instance.display_name,
        "tool_type_id": str(instance.tool_type_id),
        "status": instance.status,
        "url": instance.url,
        "public_url": instance.public_url,
        "port": instance.port,
        "container_id": instance.container_id,
        "container_name": instance.container_name,
        "compose_path": instance.compose_path,
        "workspace_id": str(instance.workspace_id) if instance.workspace_id else None,
        "selected_config_profile_id": str(instance.selected_config_profile_id) if instance.selected_config_profile_id else None,
        "ssh_key_ids": instance.ssh_key_ids,
        "created_at": instance.created_at.isoformat() if instance.created_at else None,
        "last_started_at": instance.last_started_at.isoformat() if instance.last_started_at else None,
        "docker_status": docker_status,
    }


@router.patch(
    "/{project_id}/repositories/{repo_id}/instances/{instance_id}",
    summary="Rename instance",
    description="Update the display name of a tool instance.",
)
async def rename_instance(
    project_id: uuid.UUID,
    repo_id: uuid.UUID,
    instance_id: uuid.UUID,
    data: dict,
    user_id: uuid.UUID = Depends(get_current_user_id),
    session: AsyncSession = Depends(get_db_session),
) -> dict:
    _user = await _get_user(session, user_id)
    _project = await _get_owned_project(project_id, user_id, session)

    display_name = data.get("display_name", "").strip()
    if not display_name:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="display_name is required",
        )

    try:
        instance = await rename_tool_instance(
            session, user_id, project_id, repo_id, instance_id, display_name
        )
    except ValueError as exc:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)
        )

    return {
        "id": str(instance.id),
        "name": instance.name,
        "display_name": instance.display_name,
    }


@router.get(
    "/{project_id}/repositories/{repo_id}/instances/{instance_id}/logs",
    summary="Get instance logs",
    description="Get container logs for a tool instance.",
)
async def get_instance_logs(
    project_id: uuid.UUID,
    repo_id: uuid.UUID,
    instance_id: uuid.UUID,
    tail: int = 100,
    user_id: uuid.UUID = Depends(get_current_user_id),
    session: AsyncSession = Depends(get_db_session),
) -> dict:
    _user = await _get_user(session, user_id)
    _project = await _get_owned_project(project_id, user_id, session)

    instance = await session.get(ToolInstance, instance_id)
    if instance is None or instance.repository_id != repo_id:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND, detail="instance not found"
        )

    if not instance.container_id:
        return {"logs": "No container running"}

    logs = get_container_logs(instance.container_id, tail)
    return {"logs": logs}


@router.post(
    "/{project_id}/repositories/{repo_id}/instances/{instance_id}/recreate-tunnel",
    summary="Recreate tunnel",
    description="Recreate the temporary Cloudflare tunnel for a running instance.",
)
async def recreate_tunnel_endpoint(
    project_id: uuid.UUID,
    repo_id: uuid.UUID,
    instance_id: uuid.UUID,
    user_id: uuid.UUID = Depends(get_current_user_id),
    session: AsyncSession = Depends(get_db_session),
) -> dict:
    _user = await _get_user(session, user_id)
    _project = await _get_owned_project(project_id, user_id, session)
    try:
        return await recreate_instance_tunnel(
            session, user_id, project_id, repo_id, instance_id
        )
    except ValueError as exc:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(exc))
    except RuntimeError as exc:
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(exc)
        )


@router.get(
    "/{project_id}/repositories/{repo_id}/instances/{instance_id}/health",
    summary="Check instance health",
    description="Check container and tunnel health for an instance.",
)
async def check_instance_tunnel_health(
    project_id: uuid.UUID,
    repo_id: uuid.UUID,
    instance_id: uuid.UUID,
    user_id: uuid.UUID = Depends(get_current_user_id),
    session: AsyncSession = Depends(get_db_session),
) -> dict:
    _user = await _get_user(session, user_id)
    _project = await _get_owned_project(project_id, user_id, session)

    instance = await session.get(ToolInstance, instance_id)
    if instance is None or instance.repository_id != repo_id:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND, detail="instance not found"
        )

    container_info = {"status": "not_found", "exit_code": None, "health": None}
    if instance.container_id:
        container_info = get_container_status(instance.container_id)

    response = {
        "healthy": False,
        "container_status": container_info["status"],
        "container_health": container_info["health"],
        "tunnel_status": "not_applicable",
        "tunnel_status_code": None,
        "probe_status": "not_applicable",
        "last_probe_output": None,
        "error": None,
    }
    if instance.status == "probing":
        response["probe_status"] = "pending"
    elif instance.probe_result:
        response["probe_status"] = "success" if instance.probe_result.get("success") else "failed"
        response["last_probe_output"] = "\n".join(instance.probe_result.get("logs", []))
    if instance.url and instance.status in ("running", "unhealthy"):
        tunnel_health = check_tunnel_health(instance.url)
        response["tunnel_status"] = tunnel_health["tunnel_status"]
        response["tunnel_status_code"] = tunnel_health.get("status_code")
        if tunnel_health.get("error"):
            response["error"] = tunnel_health["error"]
    container_healthy = container_info["status"] == "running"
    if instance.url:
        response["healthy"] = container_healthy and response["tunnel_status"] == "healthy"
    else:
        response["healthy"] = container_healthy
    if not container_healthy:
        response["error"] = f"Container is {container_info['status']}"
        if container_info["exit_code"] is not None:
            response["error"] += f" (exit code: {container_info['exit_code']})"
    return response


@router.get(
    "/{project_id}/repositories/{repo_id}/instances/{instance_id}/events",
    summary="Get instance events history",
    description="Get lifecycle event history for a tool instance.",
)
async def get_instance_events(
    project_id: uuid.UUID,
    repo_id: uuid.UUID,
    instance_id: uuid.UUID,
    limit: int = 50,
    user_id: uuid.UUID = Depends(get_current_user_id),
    session: AsyncSession = Depends(get_db_session),
) -> list[dict]:
    from sqlalchemy import select
    from src.models import InstanceEvent

    _user = await _get_user(session, user_id)
    _project = await _get_owned_project(project_id, user_id, session)

    instance = await session.get(ToolInstance, instance_id)
    if instance is None or instance.repository_id != repo_id:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND, detail="instance not found"
        )

    result = await session.execute(
        select(InstanceEvent)
        .where(InstanceEvent.instance_id == instance_id)
        .order_by(InstanceEvent.created_at.desc())
        .limit(limit)
    )
    rows = result.scalars().all()

    return [
        {
            "id": str(row.id),
            "event_type": row.event_type,
            "status": row.status,
            "message": row.message,
            "metadata": row.event_metadata,
            "created_at": row.created_at.isoformat() if row.created_at else None,
        }
        for row in rows
    ]


@router.api_route(
    "/{project_id}/repositories/{repo_id}/instances/{instance_id}/proxy/{path:path}",
    methods=["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"],
    summary="Proxy to instance",
    description="Proxy HTTP requests to a running tool instance.",
)
async def proxy_to_instance(
    request: Request,
    project_id: uuid.UUID,
    repo_id: uuid.UUID,
    instance_id: uuid.UUID,
    path: str = "",
    user_id: uuid.UUID = Depends(get_current_user_id),
    session: AsyncSession = Depends(get_db_session),
) -> Response:
    instance = await session.get(ToolInstance, instance_id)
    if instance is None or instance.repository_id != repo_id:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND, detail="instance not found"
        )

    if instance.owner_id != user_id:
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="not authorized to access this instance",
        )

    if instance.status != "running" or not instance.container_name:
        raise HTTPException(
            status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
            detail="instance is not running",
        )

    target_url = f"http://{instance.container_name}:{instance.port}"
    if path:
        target_url += f"/{path}"

    query_string = str(request.query_params)
    if query_string:
        target_url += f"?{query_string}"

    headers = dict(request.headers)
    headers.pop("host", None)
    headers.pop("cookie", None)

    try:
        async with httpx.AsyncClient() as client:
            body = await request.body()
            response = await client.request(
                method=request.method,
                url=target_url,
                headers=headers,
                content=body,
                follow_redirects=False,
                timeout=30.0,
            )
    except Exception as exc:
        logger.error("Proxy error: %s", exc)
        raise HTTPException(
            status_code=status.HTTP_502_BAD_GATEWAY,
            detail=f"failed to reach instance: {exc}",
        )

    response_headers = dict(response.headers)
    for header in ["content-encoding", "transfer-encoding", "connection"]:
        response_headers.pop(header, None)

    return Response(
        content=response.content,
        status_code=response.status_code,
        headers=response_headers,
    )
