import OpenAI from 'openai';
import { toFile } from 'openai/uploads';

export const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });

export type RecipeAnalysis = {
  ingredients: Array<{ name: string; quantity: string | null; unit: string | null; notes?: string | null }>;
  prep_steps: string[];
  cooking_steps: string[];
  detailed?: {
    prep: Array<{ title: string; body: string }>;
    cook: Array<{ title: string; body: string }>;
  };
};

export async function uploadImageToOpenAI(bytes: Uint8Array, filename = 'image.jpg') {
  const file = await openai.files.create({
    file: await toFile(bytes, filename, { type: 'image/jpeg' }),
    purpose: 'vision',
  });
  return file;
}

export async function analyzeCookingImages(fileIds: string[]): Promise<RecipeAnalysis> {
  const system = `You are a culinary expert. Analyze ONLY the attached video. 
Return STRICT JSON with keys: ingredients, prep_steps, cooking_steps. 
- ingredients: array of { name, quantity (string|null), unit (string|null), notes (string|null) }
- prep_steps: array of strings
- cooking_steps: array of strings
Do not invent details not visible or clearly inferable. If unknown, use null.`;

  const resp = await openai.responses.create({
    model: process.env.OPENAI_IMAGE_MODEL || process.env.OPENAI_MODEL || 'gpt-4o-mini',
    input: [
      {
        role: 'system',
        content: [{ type: 'input_text', text: system }],
      },
      {
        role: 'user',
        content: [
          // Provide multiple thumbnails sampled from the video
          ...fileIds.map((id) => ({ type: 'input_image' as const, image: { file_id: id }, detail: 'high' as const })),
          {
            type: 'input_text',
            text: 'Extract ingredients, prep steps, and cooking steps. Output only JSON. No prose.',
          },
        ],
      },
    ],
    temperature: 0.2,
  });

  // The SDK exposes a helper for text output
  const text = (resp as any).output_text || (resp as any).content?.[0]?.text || '';
  try {
    return JSON.parse(text);
  } catch {
    // Try to salvage JSON if the model added code fences
    const m = text.match(/\{[\s\S]*\}/);
    if (m) {
      return JSON.parse(m[0]);
    }
    throw new Error('Model did not return valid JSON');
  }
}

export async function analyzeCookingThumbnails(images: Uint8Array[], description?: string): Promise<RecipeAnalysis> {
  const system = `You are a culinary expert. Analyze the attached images (ordered video thumbnails) and optional description.
Return STRICT JSON with keys: ingredients, prep_steps, cooking_steps.
- ingredients: array of { name, quantity (string|null), unit (string|null), notes (string|null) }
- prep_steps: array of strings
- cooking_steps: array of strings
Filling gaps: When exact details are not stated, infer reasonable approximations from visuals and common practice while preserving the spirit of the original recipe.
- Provide approximate quantities using a '~' prefix or add '(approx)' in notes when inferred from images.
- If a likely temperature/time/equipment is needed, infer a sensible default and mark as '(approx)'.
If truly unknowable, use null.`;

  const contentImages = images.map((bytes) => ({
    type: 'input_image' as const,
    image_url: `data:image/jpeg;base64,${Buffer.from(bytes).toString('base64')}`,
    detail: 'high' as const,
  }));

  const resp = await openai.responses.create({
    model: process.env.OPENAI_IMAGE_MODEL || process.env.OPENAI_MODEL || 'gpt-4o-mini',
    input: [
      { role: 'system', content: [{ type: 'input_text', text: system }] },
      {
        role: 'user',
        content: [
          ...(description && description.trim()
            ? ([{ type: 'input_text' as const, text: `Caption/Description: ${description}` }] as const)
            : ([] as const)),
          ...contentImages,
          { type: 'input_text', text: 'Extract ingredients, prep steps, and cooking steps. Output only JSON. No prose.' },
        ],
      },
    ],
    temperature: 0.2,
  });

  const text = (resp as any).output_text || (resp as any).content?.[0]?.text || '';
  try {
    return JSON.parse(text);
  } catch {
    const m = text.match(/\{[\s\S]*\}/);
    if (m) return JSON.parse(m[0]);
    throw new Error('Model did not return valid JSON');
  }
}

export async function transcribeAudioBytes(audioBytes: Uint8Array): Promise<string> {
  const model = process.env.OPENAI_TRANSCRIBE_MODEL || 'gpt-4o-transcribe';
  const resp = await (openai as any).audio.transcriptions.create({
    file: await toFile(audioBytes, 'audio.mp3', { type: 'audio/mpeg' }),
    model,
  });
  return (resp as any).text || '';
}

export async function analyzeFromTranscript(transcript: string): Promise<RecipeAnalysis> {
  const system = `You are a culinary expert. Analyze the provided transcript of a cooking video. \nReturn STRICT JSON with keys: ingredients, prep_steps, cooking_steps. \n- ingredients: array of { name, quantity (string|null), unit (string|null), notes (string|null) }\n- prep_steps: array of strings\n- cooking_steps: array of strings\nFilling gaps: If specifics are missing but strongly implied by context, infer reasonable approximations and mark them with a '~' prefix or '(approx)' in notes. If truly unknowable, use null.`;

  const resp = await openai.responses.create({
    model: process.env.OPENAI_MODEL || 'gpt-4o',
    input: [
      { role: 'system', content: [{ type: 'input_text', text: system }] },
      { role: 'user', content: [{ type: 'input_text', text: transcript }] },
    ],
    temperature: 0.2,
  });
  const text = (resp as any).output_text || (resp as any).content?.[0]?.text || '';
  try {
    return JSON.parse(text);
  } catch {
    const m = text.match(/\{[\s\S]*\}/);
    if (m) return JSON.parse(m[0]);
    throw new Error('Model did not return valid JSON');
  }
}

export async function analyzeFromTranscriptAndImages(
  transcript: string,
  images: Uint8Array[],
  description?: string,
): Promise<RecipeAnalysis> {
  const system = `You are a culinary expert. Analyze the provided transcript and ordered thumbnails from a cooking video.\nReturn STRICT JSON with keys: ingredients, prep_steps, cooking_steps.\n- ingredients: array of { name, quantity (string|null), unit (string|null), notes (string|null) }\n- prep_steps: array of strings\n- cooking_steps: array of strings\nFilling gaps: Combine transcript and visuals to infer missing details (quantities, temperatures, times, equipment) while preserving the spirit of the original recipe.\n- Use '~' or '(approx)' for estimated quantities/times when inferred; if truly unknowable, use null. Consider the images in order.`;

  const contentImages = images.map((bytes) => ({
    type: 'input_image' as const,
    image_url: `data:image/jpeg;base64,${Buffer.from(bytes).toString('base64')}`,
    detail: 'high' as const,
  }));

  const resp = await openai.responses.create({
    model: process.env.OPENAI_MODEL || 'gpt-4o',
    input: [
      { role: 'system', content: [{ type: 'input_text', text: system }] },
      {
        role: 'user',
        content: [
          ...(description && description.trim()
            ? ([{ type: 'input_text' as const, text: `Caption/Description: ${description}` }] as const)
            : ([] as const)),
          ...contentImages, // maintain order
          { type: 'input_text', text: transcript },
          { type: 'input_text', text: 'Extract ingredients, prep steps, and cooking steps. Output only JSON. No prose.' },
        ],
      },
    ],
    temperature: 0.2,
  });

  const text = (resp as any).output_text || (resp as any).content?.[0]?.text || '';
  try {
    return JSON.parse(text);
  } catch {
    const m = text.match(/\{[\s\S]*\}/);
    if (m) return JSON.parse(m[0]);
    throw new Error('Model did not return valid JSON');
  }
}

export async function generateRecipeTitle(input: { description?: string; transcript?: string; analysis?: RecipeAnalysis }): Promise<string> {
  const prompt = `Given the following context from a cooking video, generate a concise, appealing recipe title (3-7 words). No emojis. No quotes. Return only the title text.\n\nDescription: ${input.description || ''}\n\nTranscript: ${input.transcript || ''}\n\nIngredients: ${(input.analysis?.ingredients || []).map(i => i.name).join(', ')}`;
  const resp = await openai.responses.create({
    model: process.env.OPENAI_MODEL || 'gpt-4o-mini',
    input: [{ role: 'user', content: [{ type: 'input_text', text: prompt }] }],
    temperature: 0.4,
  });
  const text = (resp as any).output_text || (resp as any).content?.[0]?.text || '';
  return text.trim().replace(/^"|"$/g, '').slice(0, 120);
}

export async function generateDetailedInstructions(input: { description?: string; transcript?: string; analysis: RecipeAnalysis }): Promise<RecipeAnalysis['detailed']> {
  const prompt = `Create detailed, step-by-step cooking instructions in two sections: Prep and Cook.\nGuidelines:\n- Use clear, concise language with decisive verbs.\n- Include temperatures, times, quantities, and pan/surface sizes when inferable.\n- When specifics are missing, infer sensible approximations that fit the recipe's style and mark them '(approx)'.\n- Each step should have a short Title (3-10 words) and a Body (1-3 sentences).\n- Keep steps atomic; prefer 6-12 steps per section when appropriate.\nReturn STRICT JSON: { "prep": [{"title":"...","body":"..."}], "cook": [{"title":"...","body":"..."}] }\n\nContext:\nDescription: ${input.description || ''}\n\nTranscript: ${input.transcript || ''}\n\nIngredients: ${(input.analysis?.ingredients || []).map(i => i.name).join(', ')}\n\nExisting prep steps: ${(input.analysis?.prep_steps || []).join(' | ')}\nExisting cooking steps: ${(input.analysis?.cooking_steps || []).join(' | ')}`;

  const resp = await openai.responses.create({
    model: process.env.OPENAI_MODEL || 'gpt-4o-mini',
    input: [{ role: 'user', content: [{ type: 'input_text', text: prompt }] }],
    temperature: 0.3,
  });
  const text = (resp as any).output_text || (resp as any).content?.[0]?.text || '';
  let json: any;
  try {
    json = JSON.parse(text);
  } catch {
    const m = text.match(/\{[\s\S]*\}/);
    if (m) json = JSON.parse(m[0]);
  }
  if (!json || !('prep' in json) || !('cook' in json)) return undefined as any;
  return json as RecipeAnalysis['detailed'];
}


