from __future__ import annotations

import logging

from fastapi import APIRouter, Depends, Path, Query, Request
from sqlalchemy.ext.asyncio import AsyncSession

from app.api.deps import get_api_admin_user
from app.api.errors import ApiException
from app.api.responses import success_payload
from app.api.schemas import (
    AdminDbIntegrityEnvelope,
    AdminPdfQueueBulkEnqueueEnvelope,
    AdminPdfQueueRequeueEnvelope,
    AdminPdfQueueEnvelope,
    AdminDbRepairJobsEnvelope,
    AdminRepairPublicationNearDuplicatesEnvelope,
    AdminRepairPublicationNearDuplicatesRequest,
    AdminRepairPublicationLinksEnvelope,
    AdminRepairPublicationLinksRequest,
)
from app.db.models import DataRepairJob, User
from app.db.session import get_db_session
from app.logging_utils import structured_log
from app.services.domains.dbops import (
    collect_integrity_report,
    list_repair_jobs,
    run_publication_near_duplicate_repair,
    run_publication_link_repair,
)
from app.services.domains.publications import application as publication_service

logger = logging.getLogger(__name__)

router = APIRouter(prefix="/admin/db", tags=["api-admin-dbops"])


def _serialize_repair_job(job: DataRepairJob) -> dict[str, object]:
    return {
        "id": int(job.id),
        "job_name": job.job_name,
        "requested_by": job.requested_by,
        "dry_run": bool(job.dry_run),
        "status": job.status,
        "scope": dict(job.scope or {}),
        "summary": dict(job.summary or {}),
        "error_text": job.error_text,
        "started_at": job.started_at,
        "finished_at": job.finished_at,
        "created_at": job.created_at,
        "updated_at": job.updated_at,
    }


def _requested_by_value(*, payload, admin_user: User) -> str:
    from_payload = (payload.requested_by or "").strip()
    return from_payload or admin_user.email


def _serialize_pdf_queue_item(item) -> dict[str, object]:
    return {
        "publication_id": item.publication_id,
        "title": item.title,
        "display_identifier": _serialize_display_identifier(item.display_identifier),
        "pdf_url": item.pdf_url,
        "status": item.status,
        "attempt_count": item.attempt_count,
        "last_failure_reason": item.last_failure_reason,
        "last_failure_detail": item.last_failure_detail,
        "last_source": item.last_source,
        "requested_by_user_id": item.requested_by_user_id,
        "requested_by_email": item.requested_by_email,
        "queued_at": item.queued_at,
        "last_attempt_at": item.last_attempt_at,
        "resolved_at": item.resolved_at,
        "updated_at": item.updated_at,
    }


def _serialize_display_identifier(value) -> dict[str, object] | None:
    if value is None:
        return None
    return {
        "kind": value.kind,
        "value": value.value,
        "label": value.label,
        "url": value.url,
        "confidence_score": float(value.confidence_score),
    }


def _requeue_response_state(*, queued: bool) -> tuple[str, str]:
    if queued:
        return "queued", "PDF lookup queued."
    return "blocked", "PDF lookup is already queued or currently running."


def _bulk_enqueue_message(*, queued_count: int, requested_count: int) -> str:
    if requested_count == 0:
        return "No missing-PDF publications were found."
    if queued_count == 0:
        return "No publications were queued; all candidates were already in-flight."
    return f"Queued {queued_count} publication(s) for PDF lookup."


def _resolve_pdf_queue_paging(
    *,
    page: int,
    page_size: int,
    limit: int | None,
    offset: int | None,
) -> tuple[int, int, int]:
    resolved_limit = int(limit) if limit is not None else int(page_size)
    resolved_offset = int(offset) if offset is not None else max((int(page) - 1) * resolved_limit, 0)
    resolved_page = max((resolved_offset // max(resolved_limit, 1)) + 1, 1)
    return resolved_page, max(resolved_limit, 1), max(resolved_offset, 0)


def _pdf_queue_page_data(*, total_count: int, page: int, page_size: int, offset: int) -> dict[str, object]:
    return {
        "total_count": int(total_count),
        "page": int(page),
        "page_size": int(page_size),
        "has_prev": int(offset) > 0,
        "has_next": int(offset) + int(page_size) < int(total_count),
    }


@router.get(
    "/integrity",
    response_model=AdminDbIntegrityEnvelope,
)
async def get_integrity_report(
    request: Request,
    db_session: AsyncSession = Depends(get_db_session),
    admin_user: User = Depends(get_api_admin_user),
):
    report = await collect_integrity_report(db_session)
    structured_log(logger, "info", "api.admin.db.integrity_checked", admin_user_id=int(admin_user.id), status=report.get("status"), failure_count=len(report.get("failures", [])), warning_count=len(report.get("warnings", [])))
    return success_payload(request, data=report)


@router.get(
    "/repair-jobs",
    response_model=AdminDbRepairJobsEnvelope,
)
async def get_repair_jobs(
    request: Request,
    limit: int = Query(default=50, ge=1, le=200),
    db_session: AsyncSession = Depends(get_db_session),
    admin_user: User = Depends(get_api_admin_user),
):
    jobs = await list_repair_jobs(db_session, limit=limit)
    structured_log(logger, "info", "api.admin.db.repair_jobs_listed", admin_user_id=int(admin_user.id), limit=int(limit), job_count=len(jobs))
    return success_payload(
        request,
        data={"jobs": [_serialize_repair_job(job) for job in jobs]},
    )


@router.get(
    "/pdf-queue",
    response_model=AdminPdfQueueEnvelope,
)
async def get_pdf_queue(
    request: Request,
    page: int = Query(default=1, ge=1),
    page_size: int = Query(default=100, ge=1, le=500),
    limit: int | None = Query(default=None, ge=1, le=500),
    offset: int | None = Query(default=None, ge=0),
    status: str | None = Query(default=None),
    db_session: AsyncSession = Depends(get_db_session),
    admin_user: User = Depends(get_api_admin_user),
):
    normalized_status = (status or "").strip().lower() or None
    resolved_page, resolved_limit, resolved_offset = _resolve_pdf_queue_paging(
        page=page,
        page_size=page_size,
        limit=limit,
        offset=offset,
    )
    queue_page = await publication_service.list_pdf_queue_page(
        db_session,
        limit=resolved_limit,
        offset=resolved_offset,
        status=normalized_status,
    )
    structured_log(
        logger, "info", "api.admin.db.pdf_queue_listed",
        admin_user_id=int(admin_user.id),
        page=int(resolved_page),
        page_size=int(resolved_limit),
        offset=int(resolved_offset),
        status=normalized_status,
        item_count=len(queue_page.items),
        total_count=int(queue_page.total_count),
    )
    return success_payload(
        request,
        data={
            "items": [_serialize_pdf_queue_item(item) for item in queue_page.items],
            **_pdf_queue_page_data(
                total_count=queue_page.total_count,
                page=resolved_page,
                page_size=resolved_limit,
                offset=resolved_offset,
            ),
        },
    )


@router.post(
    "/pdf-queue/{publication_id}/requeue",
    response_model=AdminPdfQueueRequeueEnvelope,
)
async def requeue_pdf_lookup(
    request: Request,
    publication_id: int = Path(ge=1),
    db_session: AsyncSession = Depends(get_db_session),
    admin_user: User = Depends(get_api_admin_user),
):
    result = await publication_service.enqueue_retry_pdf_job_for_publication_id(
        db_session,
        user_id=int(admin_user.id),
        request_email=admin_user.email,
        publication_id=publication_id,
    )
    if not result.publication_exists:
        raise ApiException(
            status_code=404,
            code="publication_not_found",
            message="Publication not found.",
        )
    status, message = _requeue_response_state(queued=result.queued)
    structured_log(logger, "info", "api.admin.db.pdf_queue_requeue_requested", admin_user_id=int(admin_user.id), publication_id=int(publication_id), queued=bool(result.queued))
    return success_payload(
        request,
        data={
            "publication_id": int(publication_id),
            "queued": bool(result.queued),
            "status": status,
            "message": message,
        },
    )


@router.post(
    "/pdf-queue/requeue-all",
    response_model=AdminPdfQueueBulkEnqueueEnvelope,
)
async def requeue_all_missing_pdfs(
    request: Request,
    limit: int = Query(default=1000, ge=1, le=5000),
    db_session: AsyncSession = Depends(get_db_session),
    admin_user: User = Depends(get_api_admin_user),
):
    result = await publication_service.enqueue_all_missing_pdf_jobs(
        db_session,
        user_id=int(admin_user.id),
        request_email=admin_user.email,
        limit=limit,
    )
    structured_log(logger, "info", "api.admin.db.pdf_queue_requeue_all", admin_user_id=int(admin_user.id), limit=int(limit), requested_count=int(result.requested_count), queued_count=int(result.queued_count))
    return success_payload(
        request,
        data={
            "requested_count": int(result.requested_count),
            "queued_count": int(result.queued_count),
            "message": _bulk_enqueue_message(
                queued_count=int(result.queued_count),
                requested_count=int(result.requested_count),
            ),
        },
    )


@router.post(
    "/repairs/publication-links",
    response_model=AdminRepairPublicationLinksEnvelope,
)
async def trigger_publication_link_repair(
    payload: AdminRepairPublicationLinksRequest,
    request: Request,
    db_session: AsyncSession = Depends(get_db_session),
    admin_user: User = Depends(get_api_admin_user),
):
    try:
        result = await run_publication_link_repair(
            db_session,
            scope_mode=payload.scope_mode,
            user_id=payload.user_id,
            scholar_profile_ids=payload.scholar_profile_ids,
            dry_run=bool(payload.dry_run),
            gc_orphan_publications=bool(payload.gc_orphan_publications),
            requested_by=_requested_by_value(payload=payload, admin_user=admin_user),
        )
    except ValueError as exc:
        raise ApiException(
            status_code=400,
            code="invalid_repair_scope",
            message=str(exc),
        ) from exc
    structured_log(
        logger, "info", "api.admin.db.publication_link_repair_triggered",
        admin_user_id=int(admin_user.id),
        scope_mode=payload.scope_mode,
        target_user_id=int(payload.user_id) if payload.user_id is not None else None,
        dry_run=bool(payload.dry_run),
        gc_orphan_publications=bool(payload.gc_orphan_publications),
        job_id=int(result["job_id"]),
        status=result["status"],
    )
    return success_payload(request, data=result)


@router.post(
    "/repairs/publication-near-duplicates",
    response_model=AdminRepairPublicationNearDuplicatesEnvelope,
)
async def trigger_publication_near_duplicate_repair(
    payload: AdminRepairPublicationNearDuplicatesRequest,
    request: Request,
    db_session: AsyncSession = Depends(get_db_session),
    admin_user: User = Depends(get_api_admin_user),
):
    try:
        result = await run_publication_near_duplicate_repair(
            db_session,
            dry_run=bool(payload.dry_run),
            similarity_threshold=float(payload.similarity_threshold),
            min_shared_tokens=int(payload.min_shared_tokens),
            max_year_delta=int(payload.max_year_delta),
            max_clusters=int(payload.max_clusters),
            selected_cluster_keys=list(payload.selected_cluster_keys),
            requested_by=_requested_by_value(payload=payload, admin_user=admin_user),
        )
    except ValueError as exc:
        raise ApiException(
            status_code=400,
            code="invalid_near_duplicate_repair_request",
            message=str(exc),
        ) from exc
    structured_log(logger, "info", "api.admin.db.publication_near_duplicate_repair_triggered", admin_user_id=int(admin_user.id), dry_run=bool(payload.dry_run), selected_cluster_count=len(payload.selected_cluster_keys), job_id=int(result["job_id"]), status=result["status"])
    return success_payload(request, data=result)


DROP_PUBLICATIONS_CONFIRMATION = "DROP ALL PUBLICATIONS"


@router.post("/drop-all-publications")
async def drop_all_publications(
    request: Request,
    db_session: AsyncSession = Depends(get_db_session),
    admin_user: User = Depends(get_api_admin_user),
):
    body = await request.json()
    confirmation_text = (body.get("confirmation_text") or "").strip()
    if confirmation_text != DROP_PUBLICATIONS_CONFIRMATION:
        raise ApiException(
            status_code=400,
            code="confirmation_required",
            message=f"Type '{DROP_PUBLICATIONS_CONFIRMATION}' to confirm this destructive action.",
        )

    from sqlalchemy import delete, func, select, update

    from app.db.models import (
        Publication,
        PublicationIdentifier,
        PublicationPdfJob,
        PublicationPdfJobEvent,
        ScholarProfile,
        ScholarPublication,
    )

    count_result = await db_session.execute(select(func.count()).select_from(Publication))
    total_publications = count_result.scalar_one()

    await db_session.execute(delete(ScholarPublication))
    await db_session.execute(delete(PublicationIdentifier))
    await db_session.execute(delete(PublicationPdfJobEvent))
    await db_session.execute(delete(PublicationPdfJob))
    await db_session.execute(delete(Publication))
    await db_session.execute(
        update(ScholarProfile).values(baseline_completed=False)
    )
    await db_session.commit()

    structured_log(logger, "warning", "api.admin.db.all_publications_dropped", admin_user_id=int(admin_user.id), admin_email=admin_user.email, deleted_count=int(total_publications))
    return success_payload(
        request,
        data={
            "deleted_count": int(total_publications),
            "message": f"Dropped {total_publications} publication(s) and all related data. "
            "Scholar baselines have been reset; the next run will re-discover all publications.",
        },
    )
