import type { OpenAI } from 'openai';
import type { FastifyBaseLogger } from 'fastify';
import { SYSTEM_PROMPT, RECIPE_JSON_SCHEMA, buildUserPrompt } from './prompts';
import { computeTextCost, type CallLog } from './cost';

export interface RecipeIngredient {
  nom: string;
  quantite: string;
  notes: string | null;
  complement: boolean;
}

export interface RecipeStep {
  numero: number;
  instruction: string;
  duree_minutes: number | null;
}

export interface StructuredRecipe {
  titre: string;
  description: string;
  origine_inspiration: string;
  ingredients: RecipeIngredient[];
  etapes: RecipeStep[];
  temps_preparation: number;
  temps_cuisson: number;
  portions: number;
  difficulte: 'facile' | 'moyen' | 'difficile';
  conseils: string[];
  accord_boisson: string | null;
}

export interface GenerateRecipeOptions {
  transcription: string;
  hint?: string;
  userId?: string;
}

export interface GenerateRecipeResult {
  recipe: StructuredRecipe;
  log: CallLog;
}

const DEFAULT_MODEL = process.env.OPENAI_TEXT_MODEL || 'gpt-4o-mini';

export async function generateRecipe(
  openai: OpenAI,
  logger: FastifyBaseLogger,
  options: GenerateRecipeOptions
): Promise<GenerateRecipeResult> {
  const start = Date.now();
  const model = DEFAULT_MODEL;

  const completion = await openai.chat.completions.create({
    model,
    messages: [
      { role: 'system', content: SYSTEM_PROMPT },
      { role: 'user', content: buildUserPrompt(options.transcription, options.hint) },
    ],
    response_format: {
      type: 'json_schema',
      json_schema: RECIPE_JSON_SCHEMA,
    },
    // Léger : on veut de la créativité mais sans halluciner les ingrédients
    temperature: 0.85,
    top_p: 0.95,
    // Le modèle a tout ce qu'il faut pour répondre court ; cap de sécurité
    max_tokens: 2000,
  });

  const choice = completion.choices[0];
  if (!choice) throw new Error('Réponse OpenAI vide');

  // Avec strict mode + json_schema, refusal possible si le contenu viole les
  // règles de sécurité OpenAI
  if (choice.message.refusal) {
    throw new Error(`Recette refusée par OpenAI : ${choice.message.refusal}`);
  }

  const raw = choice.message.content;
  if (!raw) throw new Error('Contenu de la réponse vide');

  // Strict mode garantit le format, mais on parse défensivement
  let recipe: StructuredRecipe;
  try {
    recipe = JSON.parse(raw) as StructuredRecipe;
  } catch (err) {
    throw new Error(
      `Parse JSON impossible malgré strict mode : ${(err as Error).message}`
    );
  }

  // Validation post-parse minimale (le schema garantit déjà la structure)
  if (!recipe.titre || !recipe.ingredients?.length || !recipe.etapes?.length) {
    throw new Error('Recette générée incomplète');
  }

  const usage = completion.usage;
  const cachedTokens = usage?.prompt_tokens_details?.cached_tokens ?? 0;
  const cost = computeTextCost(model, {
    promptTokens: usage?.prompt_tokens,
    cachedTokens,
    completionTokens: usage?.completion_tokens,
  });

  const log: CallLog = {
    userId: options.userId,
    operation: 'recipe_generation',
    model,
    durationMs: Date.now() - start,
    costUsd: cost,
    usage: {
      promptTokens: usage?.prompt_tokens,
      cachedTokens,
      completionTokens: usage?.completion_tokens,
    },
    cacheHit: cachedTokens > 0,
  };

  logger.info(log, 'openai_recipe_generated');

  return { recipe, log };
}
