/**
 * Generate a document via the crawler service and store it in Convex storage.
 *
 * This is the model-layer helper; Convex actions should call this via a thin
 * wrapper in `convex/documents.ts`.
 */

import { fetchJson } from '../../lib/utils/type-cast-helpers';
import { internal } from '../_generated/api';
import type { Id } from '../_generated/dataModel';
import type { ActionCtx } from '../_generated/server';
import { createDebugLog } from '../lib/debug_log';
import { UpstreamHttpError } from '../lib/errors/upstream_http_error';
import { orgSlugFromId } from '../lib/helpers/org_slug';
import { sanitizeError } from '../lib/utils/sanitize_secrets';
import {
  buildDownloadUrl,
  buildRequestBody,
  getCrawlerUrl,
  getEndpointPath,
  getOutputInfo,
} from './generate_document_helpers';
import type { GenerateDocumentArgs, GenerateDocumentResult } from './types';

const debugLog = createDebugLog('DEBUG_DOCUMENTS', '[Documents]');

/**
 * Generate a document (PDF/image) using the crawler and upload it to storage.
 */
export async function generateDocument(
  ctx: ActionCtx,
  args: GenerateDocumentArgs,
): Promise<GenerateDocumentResult> {
  const crawlerUrl = getCrawlerUrl();

  const endpointPath = getEndpointPath(args.sourceType, args.outputFormat);
  const apiUrl = `${crawlerUrl}${endpointPath}`;
  const orgSlug = await orgSlugFromId(ctx, args.organizationId);

  const requestBody = buildRequestBody(
    args.sourceType,
    args.outputFormat,
    args.content,
    args.pdfOptions,
    args.imageOptions,
    args.urlOptions,
    args.extraCss,
    args.wrapInTemplate,
  );

  debugLog('documents.generateDocument start', {
    fileName: args.fileName,
    sourceType: args.sourceType,
    outputFormat: args.outputFormat,
    contentLength: args.content.length,
    wrapInTemplate: requestBody.wrap_in_template,
    contentPreview: args.content.slice(0, 100),
  });

  const response = await fetch(apiUrl, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'x-tale-org': orgSlug,
    },
    body: JSON.stringify(requestBody),
  });

  if (!response.ok) {
    const errorText = await response.text().catch(() => '');
    // Route through the typed wrapper: sanitises the body snippet (no
    // raw upstream payload in logs), classifies retryable vs permanent
    // by status, and presents a safe message to the agent boundary
    // without leaking the crawler's response details.
    throw UpstreamHttpError.fromResponse(
      'crawler',
      response,
      errorText,
      endpointPath,
    );
  }

  const arrayBuffer = await response.arrayBuffer();
  const bytes = new Uint8Array(arrayBuffer);
  const size = bytes.byteLength;

  const { contentType, extension } = getOutputInfo(
    args.outputFormat,
    args.imageOptions?.imageType,
  );

  const uploadUrl = await ctx.storage.generateUploadUrl();
  const uploadResponse = await fetch(uploadUrl, {
    method: 'POST',
    headers: {
      'Content-Type': contentType,
    },
    body: bytes,
  });

  if (!uploadResponse.ok) {
    const uploadErrorText = await uploadResponse.text().catch(() => '');
    // Storage upload (Convex `_storage` via `generateUploadUrl`) is
    // neither rag nor crawler — `UpstreamHttpError`'s service union
    // doesn't cover it. Scrub via `sanitizeError` before logging so a
    // signed URL or other secret in the response body can't leak to
    // logs, then throw a generic Error with status only.
    console.error('[documents.generateDocument] upload error', {
      status: uploadResponse.status,
      statusText: uploadResponse.statusText,
      // The Convex storage response body should be terse JSON, but
      // sanitize anyway as defense in depth.
      errorText: sanitizeError(uploadErrorText, 400),
    });
    throw new Error(
      `Failed to upload generated document: HTTP ${uploadResponse.status}`,
    );
  }

  const { storageId } = await fetchJson<{ storageId: Id<'_storage'> }>(
    uploadResponse,
  );

  const safeExtension = extension || 'pdf';
  const lowerFileName = args.fileName.toLowerCase();
  const expectedSuffix = `.${safeExtension.toLowerCase()}`;
  const finalFileName = lowerFileName.endsWith(expectedSuffix)
    ? args.fileName
    : `${args.fileName}.${safeExtension}`;

  await ctx.runMutation(
    internal.file_metadata.internal_mutations.saveFileMetadata,
    {
      organizationId: args.organizationId,
      storageId,
      fileName: finalFileName,
      contentType,
      size,
      source: 'agent',
    },
  );

  // Build download URL using our custom HTTP endpoint that sets Content-Disposition
  // This ensures the downloaded file has the correct filename instead of the storage ID
  const downloadUrl = buildDownloadUrl(storageId, finalFileName);

  debugLog('documents.generateDocument success', {
    fileName: finalFileName,
    storageId,
    size,
    contentType,
    extension: safeExtension,
  });

  return {
    success: true,
    fileStorageId: storageId,
    downloadUrl,
    fileName: finalFileName,
    contentType,
    size,
    extension: safeExtension,
  };
}
