/**
 * Generate a DOCX 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 { decode as decodeBase64 } from 'base64-arraybuffer';

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, getCrawlerUrl } from './generate_document_helpers';

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

export interface DocxSection {
  type:
    | 'heading'
    | 'paragraph'
    | 'bullets'
    | 'numbered'
    | 'table'
    | 'quote'
    | 'code';
  text?: string;
  level?: number; // For headings (1-6)
  items?: string[]; // For bullets/numbered lists
  headers?: string[]; // For tables
  rows?: string[][]; // For tables
}

export interface DocxContent {
  title?: string;
  subtitle?: string;
  sections: DocxSection[];
}

export interface GenerateDocxArgs {
  organizationId: string;
  fileName: string;
  content: DocxContent;
}

export interface GenerateDocxResult {
  success: boolean;
  fileStorageId: Id<'_storage'>;
  downloadUrl: string;
  fileName: string;
  contentType: string;
  size: number;
}

/**
 * Generate a DOCX document from structured content using the crawler service.
 */
export async function generateDocx(
  ctx: ActionCtx,
  args: GenerateDocxArgs,
): Promise<GenerateDocxResult> {
  const crawlerUrl = getCrawlerUrl();
  const apiUrl = `${crawlerUrl}/api/v1/docx`;
  const orgSlug = await orgSlugFromId(ctx, args.organizationId);

  const requestBody = {
    content: args.content,
  };

  debugLog('documents.generateDocx start', {
    fileName: args.fileName,
    sectionsCount: args.content.sections.length,
  });

  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(() => '');
    throw UpstreamHttpError.fromResponse(
      'crawler',
      response,
      errorText,
      '/api/v1/docx',
    );
  }

  const result = await response.json();

  if (!result.success || !result.file_base64) {
    // Sanitise the upstream `result.error` before it lands in the
    // thrown message. The HTTP-error path above already runs through
    // `sanitizeError` via `UpstreamHttpError.fromResponse`; this
    // body-level branch is the second escape hatch and must scrub
    // too — otherwise a crawler that 200s with `{"success":false,
    // "error":"Authorization: Bearer ..."}` would leak the secret
    // straight into the agent boundary.
    const rawErr =
      typeof result.error === 'string'
        ? result.error
        : 'Failed to generate DOCX';
    throw new Error(sanitizeError(rawErr));
  }

  // Decode base64 and upload to Convex storage
  const docxArrayBuffer = decodeBase64(result.file_base64);
  const docxBytes = new Uint8Array(docxArrayBuffer);
  const contentType =
    'application/vnd.openxmlformats-officedocument.wordprocessingml.document';

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

  if (!uploadResponse.ok) {
    const uploadErrorText = await uploadResponse.text().catch(() => '');
    // Storage upload (Convex `_storage`) is not in the UpstreamHttpError
    // service union; scrub body via sanitizeError before logging so any
    // signed URL or token in the response can't leak. Throw a status-
    // only error to the caller.
    console.error('[documents.generateDocx] upload error', {
      status: uploadResponse.status,
      statusText: uploadResponse.statusText,
      errorText: sanitizeError(uploadErrorText, 400),
    });
    throw new Error(`Failed to upload DOCX: HTTP ${uploadResponse.status}`);
  }

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

  const finalFileName = args.fileName.toLowerCase().endsWith('.docx')
    ? args.fileName
    : `${args.fileName}.docx`;

  await ctx.runMutation(
    internal.file_metadata.internal_mutations.saveFileMetadata,
    {
      organizationId: args.organizationId,
      storageId,
      fileName: finalFileName,
      contentType,
      size: docxBytes.length,
      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.generateDocx success', {
    fileName: finalFileName,
    storageId,
    size: docxBytes.length,
  });

  return {
    success: true,
    fileStorageId: storageId,
    downloadUrl,
    fileName: finalFileName,
    contentType,
    size: docxBytes.length,
  };
}
