import type { SupabaseClient } from "@supabase/supabase-js";
import type { LookPayload, StyleAnalysis } from "./imisi";

const GATEWAY = "https://ai.gateway.lovable.dev/v1/chat/completions";

type Content = Array<
  { type: "text"; text: string } | { type: "image_url"; image_url: { url: string } }
>;

async function callGateway(body: Record<string, unknown>) {
  const key = process.env["LOVABLE_API_KEY"];
  if (!key) throw new Error("AI is not configured yet.");

  const response = await fetch(GATEWAY, {
    method: "POST",
    headers: { Authorization: `Bearer ${key}`, "Content-Type": "application/json" },
    body: JSON.stringify(body),
  });

  if (!response.ok) {
    const text = await response.text();
    if (response.status === 429) throw new Error("Imisi is busy right now. Please try again shortly.");
    if (response.status === 402) throw new Error("AI credits are exhausted. Please top up to continue.");
    console.error(`AI gateway failed [${response.status}]: ${text}`);
    throw new Error(`Imisi could not complete this styling right now.`);
  }
  return (await response.json()) as {
    choices: Array<{
      message: { content?: string; images?: Array<{ image_url: { url: string } }> };
    }>;
  };
}

function parseJson<T>(raw: string): T {
  const cleaned = raw
    .trim()
    .replace(/^```(?:json)?/i, "")
    .replace(/```$/, "")
    .trim();
  const start = cleaned.indexOf("{");
  const end = cleaned.lastIndexOf("}");
  return JSON.parse(start >= 0 ? cleaned.slice(start, end + 1) : cleaned) as T;
}

const STYLIST_LAWS = `You are Imisi, an AI Personal Image Consultant created by Ajoké Prestige Limited.
Non-negotiable laws:
1. You are a personal stylist, never an outfit generator. Never generic, never random.
2. Understand the person before recommending anything: their body, colouring, goals, desired perception, preferred styles, and the occasion.
3. Always explain your reasoning in warm, simple language. No fashion jargon.
4. Recommend one complete, intentional look — clothing, shoes and accessories working together.
5. African context is mandatory: Nigerian climates, occasions (owambe, church, Lagos vs Abuja corporate), fabrics (adire, aso-oke, lace, ankara), tailoring culture and locally available silhouettes. African context shapes the reasoning; it never becomes costume.
6. Perception is the outcome — say what impression the look creates.
7. Create psychological safety. Coach and encourage. Never judge, shame or compare.`;

export async function analyzePhoto(input: {
  frontImage: string;
  sideImage?: string | null;
  gender?: string | null;
  name?: string | null;
}): Promise<StyleAnalysis> {
  const content: Content = [
    {
      type: "text",
      text: `Analyse this person's photo${input.sideImage ? "s (front and side view)" : ""} as a professional image consultant and return ONLY JSON:
{"body_shape":"","face_shape":"","skin_tone":"","undertone":"","proportions":"","height":"","observations":["","",""]}
Gender identity given: ${input.gender ?? "unspecified"}. Name: ${input.name ?? "unspecified"}.
Use warm, respectful, non-judgemental language. skin_tone should describe depth and warmth (e.g. "Deep warm brown"). undertone one of warm / cool / neutral / olive. proportions describes torso-to-leg balance. height is an estimate range in cm if you can tell, otherwise "Not certain — please confirm". observations: 3-5 short, encouraging styling observations specific to this person.
If the photo is too unclear to analyse confidently, return {"error":"unclear"}.`,
    },
    { type: "image_url", image_url: { url: input.frontImage } },
  ];
  if (input.sideImage) content.push({ type: "image_url", image_url: { url: input.sideImage } });

  const data = await callGateway({
    model: "google/gemini-2.5-flash",
    messages: [
      { role: "system", content: STYLIST_LAWS },
      { role: "user", content },
    ],
  });

  const raw = data.choices?.[0]?.message?.content ?? "";
  const parsed = parseJson<StyleAnalysis & { error?: string }>(raw);
  if (parsed.error === "unclear") {
    throw new Error(
      "I couldn't see you clearly enough in that photo. Please upload a brighter, full-body photo so I can style you properly.",
    );
  }
  return {
    body_shape: parsed.body_shape ?? "",
    face_shape: parsed.face_shape ?? "",
    skin_tone: parsed.skin_tone ?? "",
    undertone: parsed.undertone ?? "",
    proportions: parsed.proportions ?? "",
    height: parsed.height ?? "",
    observations: Array.isArray(parsed.observations) ? parsed.observations.slice(0, 5) : [],
  };
}

export type LookContext = {
  occasion: string;
  weather?: string | null;
  timeOfDay?: string | null;
  location?: string | null;
  dressCode?: string | null;
  notes?: string | null;
  wantsHair: boolean;
  wantsMakeup: boolean;
};

export async function generateLook(input: {
  context: LookContext;
  profile: {
    display_name: string | null;
    gender: string | null;
    goals: string[];
    perceptions: string[];
    styles: string[];
    discover_style: boolean;
  };
  analysis: StyleAnalysis | null;
  history: string[];
}): Promise<LookPayload> {
  const brief = `PERSON
Name: ${input.profile.display_name ?? "Friend"}
Gender: ${input.profile.gender ?? "unspecified"}
Body shape: ${input.analysis?.body_shape ?? "unknown"}
Face shape: ${input.analysis?.face_shape ?? "unknown"}
Skin tone: ${input.analysis?.skin_tone ?? "unknown"} (${input.analysis?.undertone ?? "undertone unknown"})
Proportions: ${input.analysis?.proportions ?? "unknown"}
Height: ${input.analysis?.height ?? "unknown"}
Observations: ${(input.analysis?.observations ?? []).join("; ") || "none"}

INTENT
Goals: ${input.profile.goals.join(", ") || "not stated"}
Desired perception: ${input.profile.perceptions.join(", ") || "not stated"}
Preferred styles: ${input.profile.discover_style ? "Still discovering — guide them toward styles that suit their body, colouring and lifestyle" : input.profile.styles.join(", ") || "not stated"}

CONTEXT
Occasion: ${input.context.occasion}
Weather: ${input.context.weather ?? "typical Nigerian weather"}
Time of day: ${input.context.timeOfDay ?? "unspecified"}
Location: ${input.context.location ?? "Nigeria"}
Dress code: ${input.context.dressCode ?? "none stated"}
Extra notes: ${input.context.notes ?? "none"}
Hair recommendation requested: ${input.context.wantsHair}
Makeup recommendation requested: ${input.context.wantsMakeup}

PREVIOUS LOOKS (do not repeat them): ${input.history.join(" | ") || "none"}`;

  const data = await callGateway({
    model: "google/gemini-2.5-pro",
    messages: [
      { role: "system", content: STYLIST_LAWS },
      {
        role: "user",
        content: `${brief}

Work through your reasoning silently, then return ONLY JSON in exactly this shape:
{
 "title": "short evocative name for the look",
 "opening": "2 sentences spoken warmly to them by name, framing the look",
 "outfit": [{"item":"Top | Bottom | Dress | Outerwear","description":"specific garment, fabric, colour, cut","why":"why it suits this person"}],
 "shoes": {"item":"Shoes","description":"","why":""},
 "accessories": [{"item":"Bag | Jewellery | Belt | Watch | Sunglasses | Scarf | Hat","description":"","why":""}],
 ${input.context.wantsHair ? '"hair": "hairstyle direction and why",' : ""}
 ${input.context.wantsMakeup ? '"makeup": "makeup direction and why",' : ""}
 "color_analysis": "why these colours suit their skin tone and undertone, why the combination works, and the psychological impression the colours create",
 "fit_analysis": "why these silhouettes suit their body shape, how the look balances their proportions",
 "why_this_works": ["4-6 short reasons covering colour, fit, occasion, accessories and desired perception"],
 "perception_analysis": "how others will likely perceive them in this look",
 "styling_tips": ["3-5 practical, specific tips"],
 "visual_prompt": "a single detailed photography prompt describing this exact complete look worn by a person matching their described body shape, proportions, skin tone and gender, in a setting suited to the occasion"
}
Every field must be personal to this individual. Never generic.`,
      },
    ],
  });

  const payload = parseJson<LookPayload>(data.choices?.[0]?.message?.content ?? "");
  return payload;
}

function bytesToBase64(bytes: Uint8Array): string {
  let binary = "";
  const chunk = 0x8000;
  for (let i = 0; i < bytes.length; i += chunk) {
    binary += String.fromCharCode(...bytes.subarray(i, i + chunk));
  }
  return btoa(binary);
}

async function loadReferencePhoto(
  supabase: SupabaseClient,
  path: string | null | undefined,
): Promise<string | null> {
  if (!path) return null;
  try {
    const { data, error } = await supabase.storage.from("user-photos").download(path);
    if (error || !data) return null;
    const bytes = new Uint8Array(await data.arrayBuffer());
    const mime = data.type && data.type.startsWith("image/") ? data.type : "image/jpeg";
    return `data:${mime};base64,${bytesToBase64(bytes)}`;
  } catch {
    return null;
  }
}

export async function renderLookImage(
  supabase: SupabaseClient,
  userId: string,
  visualPrompt: string,
  reference?: { photoPath?: string | null; sidePhotoPath?: string | null },
): Promise<string | null> {
  try {
    const front = await loadReferencePhoto(supabase, reference?.photoPath);
    const side = front ? await loadReferencePhoto(supabase, reference?.sidePhotoPath) : null;

    const instruction = front
      ? `Using the attached reference photograph${side ? "s" : ""} of this exact person, generate a photorealistic full-body editorial photograph of THE SAME PERSON wearing the outfit described below.

IDENTITY IS NON-NEGOTIABLE: keep the same face, facial features, facial structure, eyes, nose, lips, jawline, hairline, complexion, skin tone and undertone, body shape, proportions and height as the reference photo. The face must be clearly visible, sharp, front-facing and unmistakably the same person. Do not beautify, slim, lighten, age, or alter their features in any way. Do not substitute a model.

Change ONLY the clothing, shoes, accessories, pose and background. Natural warm daylight, realistic skin texture and fabric texture, shot on a 50mm lens, no text, no logos, no watermark, not stylised, not illustrated, not AI-looking.

THE LOOK: ${visualPrompt}`
      : `Photorealistic full-body editorial fashion photograph, natural warm light, realistic skin and fabric texture, 50mm lens, no text or logos. ${visualPrompt}`;

    const content: Content = [{ type: "text", text: instruction }];
    if (front) content.push({ type: "image_url", image_url: { url: front } });
    if (side) content.push({ type: "image_url", image_url: { url: side } });

    const data = await callGateway({
      model: "google/gemini-3.1-flash-image",
      modalities: ["image", "text"],
      messages: [{ role: "user", content }],
    });
    const url = data.choices?.[0]?.message?.images?.[0]?.image_url?.url;
    if (!url) return null;

    const base64 = url.split(",")[1] ?? "";
    const binary = atob(base64);
    const bytes = new Uint8Array(binary.length);
    for (let i = 0; i < binary.length; i += 1) bytes[i] = binary.charCodeAt(i);

    const path = `${userId}/looks/${crypto.randomUUID()}.png`;
    const { error } = await supabase.storage
      .from("user-photos")
      .upload(path, bytes, { contentType: "image/png" });
    if (error) {
      console.error("Look image upload failed", error.message);
      return null;
    }
    return path;
  } catch (error) {
    console.error("Look visualisation failed", error);
    return null;
  }
}

