/**
 * AI Background Removal
 *
 * Cuts the subject out of an image using the OpenAI Images Edits endpoint with
 * `background: transparent` — the same provider, key and config the rest of the
 * brand asset generation already runs on (see getAIConfig).
 *
 * This exists because the cheap path cannot do it: the flood fill in
 * assetConversion.ts removes a flat backdrop that touches the border, which
 * covers logos and generated marks but never a photographic background. Cutting
 * a subject out of a photo needs segmentation, and this is the segmentation the
 * project already has access to.
 *
 * Callers must cache the result — every call is a paid, multi-second request.
 */

import sharp from 'sharp';
import { getAIConfig } from './aiProvider';

/** Sizes the Images Edits endpoint accepts, with their aspect ratios. */
const SUPPORTED_SIZES: { size: string; ratio: number }[] = [
  { size: '1024x1024', ratio: 1 },
  { size: '1536x1024', ratio: 1536 / 1024 },
  { size: '1024x1536', ratio: 1024 / 1536 },
];

/** Models to try, in order. gpt-image-2 matches the rest of the brand asset flows. */
const EDIT_MODELS = ['gpt-image-2', 'gpt-image-1'];

const TIMEOUT_MS = 300000;

const CUTOUT_PROMPT = [
  'Remove the background completely.',
  'Keep only the main subject, exactly as it appears — same colours, same shape,',
  'same details, same proportions. Do not redraw, restyle, crop, or invent anything.',
  'The area around the subject must be fully transparent.',
].join(' ');

/** Pick the accepted size whose aspect ratio is closest to the source image. */
function closestSize(width: number, height: number): string {
  const ratio = width / height;
  return SUPPORTED_SIZES.reduce((best, candidate) =>
    Math.abs(candidate.ratio - ratio) < Math.abs(best.ratio - ratio) ? candidate : best
  ).size;
}

/** Pull the base64 image out of the several response shapes the endpoint returns. */
function extractBase64(data: any): string {
  const image = data?.data?.[0];
  if (image) {
    const direct = image.b64_json || image.base64_data || image.base64Data || image.data;
    if (direct) return direct;
  }
  if (Array.isArray(data?.output)) {
    for (const out of data.output) {
      for (const c of out.content || []) {
        if (c.type === 'image_url' && typeof c.url === 'string') {
          const m = c.url.match(/^data:image\/\w+;base64,(.+)$/);
          if (m) return m[1];
        }
      }
    }
  }
  return '';
}

/**
 * Cut the subject out of `sourceBuffer` and return a transparent PNG at the
 * source image's original dimensions.
 *
 * The model renders at its own supported size, so the result is scaled back and
 * letterboxed onto a transparent canvas — the stored dimensions are preserved
 * and the padding is invisible in a PNG.
 *
 * @throws when no OpenAI key is configured or every model attempt fails.
 */
export async function removeBackgroundWithAI(
  sourceBuffer: Buffer,
  sourceMime: string,
  userId?: string
): Promise<Buffer> {
  const config = await getAIConfig(userId);
  const openaiKey = config.OPENAI_API_KEY;
  if (!openaiKey) {
    throw new Error(
      'OpenAI API key not configured. Set OPENAI_API_KEY in .env or add a key in Super Admin settings.'
    );
  }

  const baseUrl = (config.OPENAI_API_URL || 'https://api.openai.com/v1/chat/completions')
    .replace('/chat/completions', '')
    .replace(/\/$/, '');
  const editsUrl = `${baseUrl}/images/edits`;

  const meta = await sharp(sourceBuffer).metadata();
  const width = meta.width || 1024;
  const height = meta.height || 1024;

  const errors: string[] = [];

  for (const model of EDIT_MODELS) {
    const form = new FormData();
    form.append('model', model);
    form.append('image', new Blob([new Uint8Array(sourceBuffer)], { type: sourceMime }), 'asset.png');
    form.append('prompt', CUTOUT_PROMPT);
    form.append('size', closestSize(width, height));
    form.append('quality', 'high');
    // Transparency is only honoured for png/webp output.
    form.append('background', 'transparent');
    form.append('output_format', 'png');

    const controller = new AbortController();
    const timeoutId = setTimeout(() => controller.abort(), TIMEOUT_MS);
    const startedAt = Date.now();

    try {
      console.log(`[AIBackgroundRemoval] ${editsUrl} | model=${model} | source=${width}x${height}`);
      const response = await fetch(editsUrl, {
        method: 'POST',
        headers: { Authorization: `Bearer ${openaiKey}` },
        body: form,
        signal: controller.signal,
      });

      if (!response.ok) {
        const errorText = await response.text();
        const httpError: any = new Error(
          `Images Edits error (${response.status}) with ${model}: ${errorText}`
        );
        httpError.status = response.status;
        throw httpError;
      }

      const base64 = extractBase64(await response.json());
      if (!base64) {
        throw new Error(`${model} returned no image data`);
      }

      console.log(
        `[AIBackgroundRemoval] ${model} succeeded in ${Math.round((Date.now() - startedAt) / 1000)}s`
      );

      // Scale back onto a transparent canvas at the stored dimensions so the
      // asset keeps the size the rest of the app expects.
      return await sharp(Buffer.from(base64, 'base64'))
        .resize(width, height, {
          fit: 'contain',
          background: { r: 0, g: 0, b: 0, alpha: 0 },
        })
        .png()
        .toBuffer();
    } catch (err: any) {
      const message = err?.name === 'AbortError' ? `${model} timed out` : err?.message || String(err);
      console.warn(`[AIBackgroundRemoval] ${message}`);
      errors.push(message);

      // A rejected key fails identically for every model — stop rather than
      // burning another round trip, and say what actually needs fixing.
      if (err?.status === 401 || err?.status === 403) {
        throw new Error(
          `The image provider at ${editsUrl} rejected the API key (HTTP ${err.status}). ` +
            'Update the OpenAI key under Super Admin → AI settings, then try again. ' +
            `Provider response: ${message}`
        );
      }
      if (err?.status === 404) {
        throw new Error(
          `The image provider at ${editsUrl} does not offer the Images Edits endpoint ` +
            '(HTTP 404). Background removal needs a provider that supports image edits with ' +
            'a transparent background.'
        );
      }
    } finally {
      clearTimeout(timeoutId);
    }
  }

  throw new Error(`AI background removal failed. ${errors.join(' | ')}`);
}
