import "server-only";

import { api } from "@/lib/convex/api";
import { getAuthedConvexClient } from "@/lib/convex/route";
import {
  buildDocumentBridgeContext,
  buildDocumentQueryEnvelope,
  DocumentBridgeError,
} from "@/lib/documents/bridge";
import { executeRustBridgeQuery } from "@/lib/documents/rust-runtime";
import type { DocumentSearchFilters, DocumentSearchRequest, DocumentSearchResponse, DocumentSearchResult } from "@/types/search";

const MAX_LIMIT = 50;

const DEFAULT_FILTERS: DocumentSearchFilters = {
  titleOnly: false,
  exact: false,
  onlyCurrentPage: false,
  includeOcr: false,
  timeRange: "any",
  timeField: "updated",
};

function parseDateToIso(value: string | undefined): string | null {
  const raw = String(value ?? "").trim();
  if (!raw) {
    return null;
  }
  const date = new Date(raw);
  if (Number.isNaN(date.getTime())) {
    return null;
  }
  return date.toISOString();
}

type SearchDocumentsRustResult = {
  results: Array<{
    id: string;
    title: string;
    snippet: string;
    updatedAt?: string | null;
    createdAt?: string | null;
    matchField: "title" | "content" | "recent";
    hasOcr: boolean;
    publicPath: string;
    score: number;
    nodeId?: string | null;
    subtreeRootId?: string | null;
    evidence?: Array<{
      kind: string;
      nodeId?: string | null;
      snippet: string;
    }>;
  }>;
  enqueueAssetIds?: string[];
};

type SearchRecentRustResult = {
  items: Array<{
    documentId: string;
  }>;
};

type SearchDocumentRow = {
  id: string;
  workspace_id?: string | null;
  title?: string | null;
  raw_text?: string | null;
  created_at?: string | null;
  updated_at?: string | null;
};

type SearchMindmapRow = {
  document_id?: string | null;
  data?: unknown;
};

type SearchTableRow = {
  id: string;
  document_id?: string | null;
  title?: string | null;
};

type SearchTableContentRow = {
  table_id?: string | null;
  document_id?: string | null;
  row_hash?: string | null;
};

type SearchAssetRow = {
  id: string;
  document_id?: string | null;
  asset_type?: string | null;
  file_name?: string | null;
  mime_type?: string | null;
  ocr_text?: string | null;
  ocr_status?: string | null;
};

type RecentRow = {
  document_id: string;
  workspace_id: string;
  last_accessed_at: string;
};

function normalizeWorkspaceId(workspaceId: string | null | undefined) {
  const normalized = typeof workspaceId === "string" ? workspaceId.trim() : "";
  if (!normalized) {
    throw new DocumentBridgeError("缺少 workspaceId", 400, "VALIDATION_ERROR");
  }
  return normalized;
}

function normalizeQuery(value: string | null | undefined) {
  return String(value ?? "").trim();
}

function normalizeLimit(value: number | null | undefined) {
  if (typeof value !== "number" || !Number.isFinite(value)) {
    return 30;
  }
  return Math.max(1, Math.min(Math.floor(value), MAX_LIMIT));
}

function normalizeDocumentId(value: string | null | undefined) {
  const normalized = typeof value === "string" ? value.trim() : "";
  return normalized || null;
}

function normalizeFilters(input: DocumentSearchRequest["filters"] | undefined): DocumentSearchFilters {
  return {
    ...DEFAULT_FILTERS,
    ...input,
  };
}

function mapRecentRowsToResults(input: {
  documentIds: string[];
  docs: SearchDocumentRow[];
}): DocumentSearchResult[] {
  const docMap = new Map(input.docs.map((item) => [item.id, item]));
  return input.documentIds
    .map((documentId) => docMap.get(documentId))
    .filter((item): item is NonNullable<typeof item> => Boolean(item))
    .map((item) => ({
      id: item.id,
      title: item.title ?? "无标题",
      snippet: "",
      updatedAt: item.updated_at ?? null,
      createdAt: item.created_at ?? null,
      matchField: "recent",
      hasOcr: false,
      publicPath: `/documents/${item.id}`,
      score: 0,
      nodeId: item.id,
      subtreeRootId: item.id,
      evidence: [
        {
          kind: "recent",
          nodeId: item.id,
          snippet: item.title ?? "最近访问页面",
        },
      ],
    }));
}

function mapRustResultsToResponse(
  result: SearchDocumentsRustResult,
): DocumentSearchResult[] {
  return (result.results ?? []).map((item) => ({
    id: item.id,
    title: item.title ?? "无标题",
    snippet: item.snippet,
    updatedAt: item.updatedAt ?? null,
    createdAt: item.createdAt ?? null,
    matchField: item.matchField,
    hasOcr: Boolean(item.hasOcr),
    publicPath: item.publicPath,
    score: item.score,
    nodeId: item.nodeId ?? item.id,
    subtreeRootId: item.subtreeRootId ?? item.id,
    evidence:
      Array.isArray(item.evidence) && item.evidence.length > 0
        ? item.evidence
        : [
            {
              kind: item.matchField,
              nodeId: item.nodeId ?? item.id,
              snippet: item.snippet || item.title || "命中文档",
            },
          ],
  }));
}

async function enqueueOcrJobs(assetIds: string[]) {
  if (assetIds.length === 0) {
    return;
  }
  const { auth, client } = await getAuthedConvexClient();
  await Promise.all(
    assetIds.slice(0, 3).map((assetId) =>
      client.mutation(api.mediaAssets.enqueueExtractText, {
        userId: auth.userId,
        id: assetId,
      }).catch(() => null),
    ),
  );
}

async function loadRecentResults(input: {
  request: Request;
  workspaceId: string;
  docs: SearchDocumentRow[];
}): Promise<DocumentSearchResult[]> {
  const { client } = await getAuthedConvexClient();
  const context = await buildDocumentBridgeContext({
    request: input.request,
    workspaceId: input.workspaceId,
  });
  const envelope = buildDocumentQueryEnvelope({
    name: "search.recent",
    payload: {
      workspaceId: input.workspaceId,
      limit: 10,
      cursor: null,
    },
  });
  const recentRows = (await client.query(api.recents.listByWorkspace, {
    userId: context.actor.actorId,
    workspaceId: input.workspaceId,
    limit: 10,
  })) as RecentRow[];
  const result = await executeRustBridgeQuery<SearchRecentRustResult>({
    context,
    envelope,
    data: {
      recents: recentRows.map((row) => ({
        documentId: row.document_id,
        workspaceId: row.workspace_id,
        lastAccessedAt: row.last_accessed_at,
      })),
    },
  });

  return mapRecentRowsToResults({
    documentIds: (result.items ?? []).map((item) => item.documentId),
    docs: input.docs,
  });
}

export async function executeDocumentSearchQuery(input: {
  request: Request;
  payload: DocumentSearchRequest;
}): Promise<{
  response: DocumentSearchResponse;
  meta: {
    requestId: string;
    traceId: string;
    queryName: string;
  };
}> {
  const workspaceId = normalizeWorkspaceId(input.payload.workspaceId);
  const normalizedQuery = normalizeQuery(input.payload.query);
  const filters = normalizeFilters(input.payload.filters);
  const limit = normalizeLimit(input.payload.limit);
  const documentId = filters.onlyCurrentPage ? normalizeDocumentId(input.payload.documentId) : null;
  const { client } = await getAuthedConvexClient();
  const context = await buildDocumentBridgeContext({
    request: input.request,
    workspaceId,
  });
  const customRangeFrom = parseDateToIso(filters.customRange?.from);
  const customRangeTo = parseDateToIso(filters.customRange?.to);

  const docs = (await client.query(api.documents.listSearchDataByWorkspace, {
    workspaceId,
  })) as SearchDocumentRow[];
  const recent = await loadRecentResults({
    request: input.request,
    workspaceId,
    docs,
  });

  if (!normalizedQuery) {
    return {
      response: {
        results: [],
        recent,
      },
      meta: {
        requestId: context.requestId,
        traceId: context.traceId,
        queryName: "search.documents",
      },
    };
  }

  const [mindmaps, tables, tableRows, assets] = (await Promise.all([
    client.query(api.mindmaps.listByWorkspace, { workspaceId, includeDeleted: false }).catch(() => []),
    client.query(api.tables.listByWorkspaceForSearch, {
      userId: context.actor.actorId,
      workspaceId,
      includeArchived: false,
      limit: 3000,
    }).catch(() => []),
    client.query(api.tables.listRowsByWorkspaceForSearch, {
      userId: context.actor.actorId,
      workspaceId,
      limit: 8000,
    }).catch(() => []),
    client.query(api.mediaAssets.listSearchDataByWorkspace, {
      userId: context.actor.actorId,
      workspaceId,
      includeDeleted: false,
      limit: 5000,
    }).catch(() => []),
  ])) as [SearchMindmapRow[], SearchTableRow[], SearchTableContentRow[], SearchAssetRow[]];

  const envelope = buildDocumentQueryEnvelope({
    name: "search.documents",
    payload: {
      query: normalizedQuery,
      workspaceId,
      pageId: documentId,
      limit,
      cursor: null,
      titleOnly: filters.titleOnly,
      exact: filters.exact,
      includeOcr: filters.includeOcr,
      timeRange: filters.timeRange,
      timeField: filters.timeField,
      customRangeFrom,
      customRangeTo,
    },
  });

  const result = await executeRustBridgeQuery<SearchDocumentsRustResult>({
    context,
    envelope,
    data: {
      documents: docs.map((item) => ({
        id: item.id,
        workspaceId: item.workspace_id ?? workspaceId,
        title: item.title ?? null,
        rawText: item.raw_text ?? null,
        createdAt: item.created_at ?? null,
        updatedAt: item.updated_at ?? null,
      })),
      mindmaps: mindmaps.map((item) => ({
        documentId: item.document_id ?? "",
        data: item.data ?? null,
      })),
      tables: tables.map((item) => ({
        id: item.id,
        documentId: item.document_id ?? "",
        title: item.title ?? null,
      })),
      tableRows: tableRows.map((item) => ({
        tableId: item.table_id ?? "",
        documentId: item.document_id ?? "",
        rowHash: item.row_hash ?? null,
      })),
      assets: assets.map((item) => ({
        id: item.id,
        documentId: item.document_id ?? "",
        assetType: item.asset_type ?? null,
        fileName: item.file_name ?? null,
        mimeType: item.mime_type ?? null,
        ocrText: item.ocr_text ?? null,
        ocrStatus: item.ocr_status ?? null,
      })),
    },
  });

  await enqueueOcrJobs(result.enqueueAssetIds ?? []);

  return {
    response: {
      results: mapRustResultsToResponse(result),
      recent,
    },
    meta: {
      requestId: context.requestId,
      traceId: context.traceId,
      queryName: envelope.name,
    },
  };
}
