"""Admin endpoints for knowledge-graph management.

Provides entity CRUD, type definition management, dedup/merge,
relationship management, rebuild, placeholder handling, and statistics.

知识图谱管理接口模块。
提供实体与关系的增删改查、实体/关系类型定义管理、
重复实体检测与合并、图谱重建任务、占位符处理，
以及图谱统计与数据质量健康度检查。
"""

from __future__ import annotations

from typing import Annotated, Any

from fastapi import APIRouter, Depends, HTTPException, Request

from app.api.deps import UserContext, get_current_user
from app.api.schemas.admin_graph import (
    DuplicateListResponse,
    EntityCreateRequest,
    EntityCreateResponse,
    EntityDeleteResponse,
    EntityDetail,
    EntityListResponse,
    EntityMergeRequest,
    EntityMergeResponse,
    EntityTypeCreateRequest,
    EntityTypeListResponse,
    EntityTypeUpdateRequest,
    GraphHealthResponse,
    GraphStatsResponse,
    PlaceholderLinkRequest,
    PlaceholderLinkResponse,
    PlaceholderListResponse,
    RebuildAllResponse,
    RebuildRequest,
    RebuildResponse,
    RebuildStatusResponse,
    RelationshipCreateRequest,
    RelationshipCreateResponse,
    RelationshipListResponse,
    RelTypeCreateRequest,
    RelTypeListResponse,
    RelTypeUpdateRequest,
    EntityTypeItem,
    EntityUpdateRequest,
    RelTypeItem,
)
from app.config import settings
from app.core.graph_admin_service import GraphAdminService
from app.utils.logger import get_logger

logger = get_logger(__name__)

router = APIRouter(prefix="/admin/graph", tags=["admin-graph"])


# ── Dependency ──────────────────────────────────────────────────────────────


def _get_graph_admin_service(request: Request) -> GraphAdminService:
    """Resolve GraphAdminService from app.state."""
    return request.app.state.graph_admin_service


# ── Statistics ──────────────────────────────────────────────────────────────


@router.get("/stats", response_model=GraphStatsResponse)
async def graph_stats(
    user: Annotated[UserContext, Depends(get_current_user)],
    svc: Annotated[GraphAdminService, Depends(_get_graph_admin_service)],
) -> GraphStatsResponse:
    """Return graph-level statistics (node/relationship counts, orphans)."""
    data = await svc.get_graph_stats()
    return GraphStatsResponse(**data)


@router.get("/health", response_model=GraphHealthResponse)
async def graph_health(
    user: Annotated[UserContext, Depends(get_current_user)],
    svc: Annotated[GraphAdminService, Depends(_get_graph_admin_service)],
    request: Request,
) -> GraphHealthResponse:
    """Return data-quality health metrics.

    数据质量健康度检查：对比 ES 与 Neo4j 的文档覆盖率，
    检测孤立实体和重复实体候选数量。
    """
    # Get ES doc_ids for comparison
    es_client = request.app.state.es_client
    try:
        resp = await es_client.raw.search(
            index=settings.es_meta_index,
            body={
                "size": 10000,
                "query": {"term": {"status": "completed"}},
                "_source": False,
            },
        )
        raw = resp if isinstance(resp, dict) else resp.body
        es_doc_ids = [hit["_id"] for hit in raw["hits"]["hits"]]
    except Exception:
        es_doc_ids = []

    data = await svc.get_graph_health(es_doc_ids)
    return GraphHealthResponse(**data)


# ── Entity Type Definitions ────────────────────────────────────────────────


@router.get("/entity-types", response_model=EntityTypeListResponse)
async def list_entity_types(
    user: Annotated[UserContext, Depends(get_current_user)],
    svc: Annotated[GraphAdminService, Depends(_get_graph_admin_service)],
) -> EntityTypeListResponse:
    items = await svc.list_entity_types()
    return EntityTypeListResponse(items=[EntityTypeItem(**i) for i in items])


@router.post("/entity-types", response_model=EntityTypeItem, status_code=201)
async def create_entity_type(
    body: EntityTypeCreateRequest,
    user: Annotated[UserContext, Depends(get_current_user)],
    svc: Annotated[GraphAdminService, Depends(_get_graph_admin_service)],
) -> EntityTypeItem:
    from app.core.graph_admin_service import TypeDefinitionReadOnlyError
    try:
        result = await svc.create_entity_type(
            name=body.name,
            description=body.description,
            icon=body.icon,
            color=body.color,
            properties_schema=body.properties_schema,
        )
        return EntityTypeItem(**result, instance_count=0)
    except TypeDefinitionReadOnlyError as exc:
        raise HTTPException(status_code=400, detail=str(exc)) from exc
    except ValueError as exc:
        raise HTTPException(status_code=409, detail=str(exc)) from exc


@router.put("/entity-types/{name}")
async def update_entity_type(
    name: str,
    body: EntityTypeUpdateRequest,
    user: Annotated[UserContext, Depends(get_current_user)],
    svc: Annotated[GraphAdminService, Depends(_get_graph_admin_service)],
) -> dict[str, Any]:
    updates = body.model_dump(exclude_none=True)
    try:
        return await svc.update_entity_type(name, updates)
    except ValueError as exc:
        raise HTTPException(status_code=400, detail=str(exc)) from exc


@router.delete("/entity-types/{name}")
async def delete_entity_type(
    name: str,
    user: Annotated[UserContext, Depends(get_current_user)],
    svc: Annotated[GraphAdminService, Depends(_get_graph_admin_service)],
) -> dict[str, Any]:
    try:
        return await svc.delete_entity_type(name)
    except ValueError as exc:
        raise HTTPException(status_code=400, detail=str(exc)) from exc


# ── Relationship Type Definitions ──────────────────────────────────────────


@router.get("/relationship-types", response_model=RelTypeListResponse)
async def list_rel_types(
    user: Annotated[UserContext, Depends(get_current_user)],
    svc: Annotated[GraphAdminService, Depends(_get_graph_admin_service)],
) -> RelTypeListResponse:
    items = await svc.list_rel_types()
    return RelTypeListResponse(items=[RelTypeItem(**i) for i in items])


@router.post("/relationship-types", response_model=RelTypeItem, status_code=201)
async def create_rel_type(
    body: RelTypeCreateRequest,
    user: Annotated[UserContext, Depends(get_current_user)],
    svc: Annotated[GraphAdminService, Depends(_get_graph_admin_service)],
) -> RelTypeItem:
    from app.core.graph_admin_service import TypeDefinitionReadOnlyError
    try:
        result = await svc.create_rel_type(
            name=body.name,
            description=body.description,
            source_labels=body.source_labels,
            target_labels=body.target_labels,
        )
        return RelTypeItem(**result, instance_count=0)
    except TypeDefinitionReadOnlyError as exc:
        raise HTTPException(status_code=400, detail=str(exc)) from exc
    except ValueError as exc:
        raise HTTPException(status_code=409, detail=str(exc)) from exc


@router.put("/relationship-types/{name}")
async def update_rel_type(
    name: str,
    body: RelTypeUpdateRequest,
    user: Annotated[UserContext, Depends(get_current_user)],
    svc: Annotated[GraphAdminService, Depends(_get_graph_admin_service)],
) -> dict[str, Any]:
    updates = body.model_dump(exclude_none=True)
    try:
        return await svc.update_rel_type(name, updates)
    except ValueError as exc:
        raise HTTPException(status_code=400, detail=str(exc)) from exc


@router.delete("/relationship-types/{name}")
async def delete_rel_type(
    name: str,
    user: Annotated[UserContext, Depends(get_current_user)],
    svc: Annotated[GraphAdminService, Depends(_get_graph_admin_service)],
) -> dict[str, Any]:
    try:
        return await svc.delete_rel_type(name)
    except ValueError as exc:
        raise HTTPException(status_code=400, detail=str(exc)) from exc


# ── Entity CRUD ─────────────────────────────────────────────────────────────
# 注意：/duplicates 和 /entities/merge 必须在 /entities/{id} 之前注册，
# 否则 FastAPI 会把 "duplicates"/"merge" 当作 entity_id 路径参数匹配。


@router.get("/duplicates", response_model=DuplicateListResponse)
async def detect_duplicates(
    user: Annotated[UserContext, Depends(get_current_user)],
    svc: Annotated[GraphAdminService, Depends(_get_graph_admin_service)],
    label: str | None = None,
    threshold: float = 0.85,
    limit: int = 50,
) -> DuplicateListResponse:
    candidates = await svc.detect_duplicates(label=label, threshold=threshold, limit=limit)
    return DuplicateListResponse(candidates=candidates, total=len(candidates))


@router.post("/entities/merge", response_model=EntityMergeResponse)
async def merge_entities(
    body: EntityMergeRequest,
    user: Annotated[UserContext, Depends(get_current_user)],
    svc: Annotated[GraphAdminService, Depends(_get_graph_admin_service)],
) -> EntityMergeResponse:
    try:
        result = await svc.merge_entities(
            primary_id=body.primary_id,
            secondary_id=body.secondary_id,
            add_alias=body.add_alias,
        )
        return EntityMergeResponse(**result)
    except ValueError as exc:
        raise HTTPException(status_code=400, detail=str(exc)) from exc


@router.get("/entities", response_model=EntityListResponse)
async def list_entities(
    user: Annotated[UserContext, Depends(get_current_user)],
    svc: Annotated[GraphAdminService, Depends(_get_graph_admin_service)],
    label: str | None = None,
    name: str | None = None,
    page: int = 1,
    page_size: int = 20,
    sort_by: str = "connection_count",
) -> EntityListResponse:
    data = await svc.list_entities(
        label=label, name=name, page=page, page_size=page_size, sort_by=sort_by,
    )
    return EntityListResponse(**data)


@router.get("/entities/{entity_id}", response_model=EntityDetail)
async def get_entity_detail(
    entity_id: str,
    user: Annotated[UserContext, Depends(get_current_user)],
    svc: Annotated[GraphAdminService, Depends(_get_graph_admin_service)],
) -> EntityDetail:
    detail = await svc.get_entity_detail(entity_id)
    if detail is None:
        raise HTTPException(status_code=404, detail="Entity not found")
    return EntityDetail(**detail)


@router.post("/entities", response_model=EntityCreateResponse, status_code=201)
async def create_entity(
    body: EntityCreateRequest,
    user: Annotated[UserContext, Depends(get_current_user)],
    svc: Annotated[GraphAdminService, Depends(_get_graph_admin_service)],
) -> EntityCreateResponse:
    try:
        result = await svc.create_entity(label=body.label, properties=body.properties)
        return EntityCreateResponse(**result)
    except ValueError as exc:
        code = 409 if "already exists" in str(exc) else 400
        raise HTTPException(status_code=code, detail=str(exc)) from exc


@router.put("/entities/{entity_id}")
async def update_entity(
    entity_id: str,
    body: EntityUpdateRequest,
    user: Annotated[UserContext, Depends(get_current_user)],
    svc: Annotated[GraphAdminService, Depends(_get_graph_admin_service)],
) -> dict[str, Any]:
    try:
        return await svc.update_entity(entity_id, body.properties)
    except ValueError as exc:
        raise HTTPException(status_code=404, detail=str(exc)) from exc


@router.delete("/entities/{entity_id}", response_model=EntityDeleteResponse)
async def delete_entity(
    entity_id: str,
    user: Annotated[UserContext, Depends(get_current_user)],
    svc: Annotated[GraphAdminService, Depends(_get_graph_admin_service)],
) -> EntityDeleteResponse:
    result = await svc.delete_entity(entity_id)
    return EntityDeleteResponse(**result)


# ── Rebuild ─────────────────────────────────────────────────────────────────


@router.post("/rebuild", response_model=RebuildResponse)
async def rebuild_doc_graphs(
    body: RebuildRequest,
    user: Annotated[UserContext, Depends(get_current_user)],
) -> RebuildResponse:
    """对指定文档重新构建知识图谱，通过 Celery 异步执行。"""
    from app.tasks.graph_task import rebuild_document_graphs_task

    task = rebuild_document_graphs_task.delay(body.doc_ids)
    return RebuildResponse(task_id=task.id, doc_count=len(body.doc_ids))


@router.post("/rebuild-all", response_model=RebuildAllResponse)
async def rebuild_all_graphs(
    user: Annotated[UserContext, Depends(get_current_user)],
    request: Request,
) -> RebuildAllResponse:
    """对所有已完成入库的文档重新构建知识图谱。"""
    from app.tasks.graph_task import rebuild_all_graph_task

    # Count total docs for the response
    es_client = request.app.state.es_client
    try:
        resp = await es_client.raw.count(
            index=settings.es_meta_index,
            body={"query": {"term": {"status": "completed"}}},
        )
        raw = resp if isinstance(resp, dict) else resp.body
        total = raw.get("count", 0)
    except Exception:
        total = 0

    task = rebuild_all_graph_task.delay()
    return RebuildAllResponse(task_id=task.id, total_docs=total)


@router.get("/rebuild-status/{task_id}", response_model=RebuildStatusResponse)
async def rebuild_status(
    task_id: str,
    user: Annotated[UserContext, Depends(get_current_user)],
) -> RebuildStatusResponse:
    """查询图谱重建任务的执行状态与进度。"""
    from celery.result import AsyncResult

    from app.tasks.celery_app import celery_app

    result = AsyncResult(task_id, app=celery_app)

    if result.state == "PENDING":
        return RebuildStatusResponse(task_id=task_id, status="PENDING")
    elif result.state in ("STARTED", "PROCESSING"):
        info = result.info or {}
        return RebuildStatusResponse(
            task_id=task_id,
            status="PROCESSING",
            progress=info.get("progress", 0.0) if isinstance(info, dict) else 0.0,
            current=info.get("current", 0) if isinstance(info, dict) else 0,
            total=info.get("total", 0) if isinstance(info, dict) else 0,
            current_doc_id=info.get("current_doc_id", "") if isinstance(info, dict) else "",
        )
    elif result.state == "SUCCESS":
        info = result.result or {}
        return RebuildStatusResponse(
            task_id=task_id, status="COMPLETED", progress=1.0,
            total=info.get("total", 0) if isinstance(info, dict) else 0,
            current=info.get("total", 0) if isinstance(info, dict) else 0,
        )
    elif result.state == "FAILURE":
        return RebuildStatusResponse(
            task_id=task_id, status="FAILED", error=str(result.result),
        )
    else:
        return RebuildStatusResponse(task_id=task_id, status=result.state)


# ── Relationships ───────────────────────────────────────────────────────────


@router.get("/relationships", response_model=RelationshipListResponse)
async def list_relationships(
    user: Annotated[UserContext, Depends(get_current_user)],
    svc: Annotated[GraphAdminService, Depends(_get_graph_admin_service)],
    type: str | None = None,
    source_id: str | None = None,
    target_id: str | None = None,
    page: int = 1,
    page_size: int = 20,
) -> RelationshipListResponse:
    data = await svc.list_relationships(
        rel_type=type, source_id=source_id, target_id=target_id,
        page=page, page_size=page_size,
    )
    return RelationshipListResponse(**data)


@router.post("/relationships", response_model=RelationshipCreateResponse, status_code=201)
async def create_relationship(
    body: RelationshipCreateRequest,
    user: Annotated[UserContext, Depends(get_current_user)],
    svc: Annotated[GraphAdminService, Depends(_get_graph_admin_service)],
) -> RelationshipCreateResponse:
    try:
        result = await svc.create_relationship(
            source_id=body.source_id, target_id=body.target_id,
            rel_type=body.type, properties=body.properties,
        )
        return RelationshipCreateResponse(**result)
    except ValueError as exc:
        raise HTTPException(status_code=400, detail=str(exc)) from exc


@router.delete("/relationships/{rel_id}")
async def delete_relationship(
    rel_id: str,
    user: Annotated[UserContext, Depends(get_current_user)],
    svc: Annotated[GraphAdminService, Depends(_get_graph_admin_service)],
) -> dict[str, Any]:
    deleted = await svc.delete_relationship(rel_id)
    if not deleted:
        raise HTTPException(status_code=404, detail="Relationship not found")
    return {"id": rel_id, "deleted": True}


# ── Placeholders ────────────────────────────────────────────────────────────


@router.get("/placeholders", response_model=PlaceholderListResponse)
async def list_placeholders(
    user: Annotated[UserContext, Depends(get_current_user)],
    svc: Annotated[GraphAdminService, Depends(_get_graph_admin_service)],
) -> PlaceholderListResponse:
    items = await svc.list_placeholders()
    return PlaceholderListResponse(items=items, total=len(items))


@router.post("/placeholders/{placeholder_id}/link", response_model=PlaceholderLinkResponse)
async def link_placeholder(
    placeholder_id: str,
    body: PlaceholderLinkRequest,
    user: Annotated[UserContext, Depends(get_current_user)],
    svc: Annotated[GraphAdminService, Depends(_get_graph_admin_service)],
) -> PlaceholderLinkResponse:
    try:
        result = await svc.link_placeholder(placeholder_id, body.real_doc_id)
        return PlaceholderLinkResponse(**result)
    except ValueError as exc:
        raise HTTPException(status_code=400, detail=str(exc)) from exc


@router.delete("/placeholders/{placeholder_id}")
async def delete_placeholder(
    placeholder_id: str,
    user: Annotated[UserContext, Depends(get_current_user)],
    svc: Annotated[GraphAdminService, Depends(_get_graph_admin_service)],
) -> dict[str, Any]:
    deleted = await svc.delete_placeholder(placeholder_id)
    if not deleted:
        raise HTTPException(status_code=404, detail="Placeholder not found")
    return {"id": placeholder_id, "deleted": True}
