import type { CharacterSpec, GenImage } from './types';
import { readImageUrl } from './storage';

export type Provider = 'mock' | 'gpt';

export const GPT_TEXT_MODEL = process.env.GPT_TEXT_MODEL || 'gpt-5.5';
export const GPT_IMAGE_MODEL = process.env.GPT_IMAGE_MODEL || 'gpt-image-2';
const GPT_API_BASE = process.env.GPT_API_BASE || 'https://api.openai.com/v1';

export function detectProvider(): Provider {
  return process.env.OPENAI_API_KEY ? 'gpt' : 'mock';
}

// Mock：返回 SVG 占位图（data URL），不真的生图
export async function generateMock(opts: {
  sessionId: string;
  prompt: string;
  count: number;
}): Promise<GenImage[]> {
  const palette = ['#ff6b35', '#3b82f6', '#10b981', '#a855f7', '#f59e0b', '#ec4899', '#06b6d4', '#84cc16', '#ef4444'];
  return Array.from({ length: opts.count }).map((_, i) => {
    const c = palette[i % palette.length];
    const c2 = palette[(i + 3) % palette.length];
    const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
      <defs><linearGradient id="g${i}" x1="0%" y1="0%" x2="100%" y2="100%">
        <stop offset="0%" stop-color="${c}"/><stop offset="100%" stop-color="${c2}"/>
      </linearGradient></defs>
      <rect width="512" height="512" fill="url(#g${i})"/>
      <circle cx="256" cy="220" r="120" fill="white" opacity="0.85"/>
      <ellipse cx="220" cy="200" rx="10" ry="14" fill="#111"/>
      <ellipse cx="292" cy="200" rx="10" ry="14" fill="#111"/>
      <path d="M210 250 Q256 285 302 250" stroke="#111" stroke-width="6" fill="none" stroke-linecap="round"/>
      <rect x="160" y="340" width="192" height="130" rx="20" fill="white" opacity="0.85"/>
      <text x="256" y="500" text-anchor="middle" font-family="monospace" font-size="20" font-weight="bold" fill="white">MOCK #${i + 1}</text>
    </svg>`;
    const dataUrl = `data:image/svg+xml;base64,${Buffer.from(svg).toString('base64')}`;
    return {
      id: `img_${opts.sessionId}_${i}`,
      url: dataUrl,
      prompt: opts.prompt,
      status: 'pending' as const,
      meta: { provider: 'mock', index: i },
    };
  });
}

async function imageResponseToDataUrl(payload: unknown): Promise<string> {
  const data = payload as { data?: Array<{ b64_json?: string; url?: string }> };
  const first = data.data?.[0];
  if (!first) throw new Error('GPT image response missing data');
  if (first.b64_json) return `data:image/png;base64,${first.b64_json}`;
  if (first.url) {
    const res = await fetch(first.url);
    if (!res.ok) throw new Error(`GPT image URL fetch ${res.status}: ${await res.text()}`);
    const type = res.headers.get('content-type')?.split(';')[0] || 'image/png';
    const buf = Buffer.from(await res.arrayBuffer());
    return `data:${type};base64,${buf.toString('base64')}`;
  }
  throw new Error('GPT image response missing b64_json or url');
}

export async function generateGptImages(opts: {
  sessionId: string;
  prompt: string;
  count: number;
  refImages?: string[];
}): Promise<GenImage[]> {
  const key = process.env.OPENAI_API_KEY;
  if (!key) throw new Error('OPENAI_API_KEY missing');

  const refHint = opts.refImages?.length
    ? `\n参考图 URL，用于保持角色一致：\n${opts.refImages.join('\n')}`
    : '';

  const calls = Array.from({ length: opts.count }).map(async (_, i) => {
    const res = await fetch(`${GPT_API_BASE}/images/generations`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        Authorization: `Bearer ${key}`,
      },
      body: JSON.stringify({
        model: GPT_IMAGE_MODEL,
        prompt: `${opts.prompt}${refHint}`,
        n: 1,
        size: '1024x1024',
      }),
    });
    if (!res.ok) throw new Error(`GPT image ${res.status}: ${await res.text()}`);
    const data = await res.json();
    return {
      id: `img_${opts.sessionId}_${i}`,
      url: await imageResponseToDataUrl(data),
      prompt: opts.prompt,
      status: 'pending' as const,
      meta: { provider: 'gpt', model: GPT_IMAGE_MODEL, index: i },
    };
  });

  return Promise.all(calls);
}

function readResponseText(data: {
  output_text?: string;
  output?: Array<{ content?: Array<{ text?: string }> }>;
}): string {
  return data.output_text || data.output?.flatMap(item => item.content ?? []).map(item => item.text ?? '').join('') || '';
}

export async function generateGptImageEdit(opts: {
  sessionId: string;
  prompt: string;
  anchorImage: Buffer | string;
  anchorMime?: string;
  anchorFilename?: string;
  size?: '1024x1024' | '1024x1536' | '1536x1024';
}): Promise<GenImage> {
  const key = process.env.OPENAI_API_KEY;
  if (!key) throw new Error('OPENAI_API_KEY missing');

  const source = typeof opts.anchorImage === 'string'
    ? await readImageUrl(opts.anchorImage)
    : {
      buf: opts.anchorImage,
      type: opts.anchorMime || 'image/png',
      filename: opts.anchorFilename || 'anchor.png',
    };

  if (source.type.includes('svg')) {
    throw new Error('GPT image edits require a raster anchor image; regenerate the source image with GPT first');
  }

  const form = new FormData();
  form.set('model', process.env.GPT_IMAGE_EDIT_MODEL || GPT_IMAGE_MODEL);
  form.set('prompt', opts.prompt);
  form.set('size', opts.size || '1024x1024');
  const imageBytes = source.buf.buffer.slice(source.buf.byteOffset, source.buf.byteOffset + source.buf.byteLength) as ArrayBuffer;
  form.set('image', new Blob([imageBytes], { type: source.type }), source.filename);

  const res = await fetch(`${GPT_API_BASE}/images/edits`, {
    method: 'POST',
    headers: { Authorization: `Bearer ${key}` },
    body: form,
  });
  if (!res.ok) throw new Error(`GPT image edit ${res.status}: ${await res.text()}`);
  const data = await res.json();
  return {
    id: `img_${opts.sessionId}_edit`,
    url: await imageResponseToDataUrl(data),
    prompt: opts.prompt,
    status: 'pending',
    meta: { provider: 'gpt', model: process.env.GPT_IMAGE_EDIT_MODEL || GPT_IMAGE_MODEL, edit: true },
  };
}

export async function generateGptJson<T>(opts: {
  prompt: string;
  fallback: T;
}): Promise<T> {
  const key = process.env.OPENAI_API_KEY;
  if (!key) return opts.fallback;

  const res = await fetch(`${GPT_API_BASE}/responses`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${key}`,
    },
    body: JSON.stringify({
      model: GPT_TEXT_MODEL,
      input: opts.prompt,
      text: { format: { type: 'json_object' } },
    }),
  });
  if (!res.ok) throw new Error(`GPT text ${res.status}: ${await res.text()}`);
  const data = await res.json() as {
    output_text?: string;
    output?: Array<{ content?: Array<{ text?: string }> }>;
  };
  const text = readResponseText(data);
  return text.trim() ? JSON.parse(text) as T : opts.fallback;
}

function genericCharacterSpec(imageUrl: string, userHint?: string): CharacterSpec {
  const now = Date.now();
  return {
    name: userHint?.trim() || '上传主体玩具',
    oneLiner: userHint?.trim() || '基于用户上传主体图复刻的玩具 IP',
    targetUser: '潮玩/礼品/品牌周边用户',
    speciesShape: '根据上传图识别的玩具主体形态',
    bodyRatio: '保持上传图中的头身比例、姿态和整体轮廓',
    faceFeatures: '保持上传图中的五官、表情和识别特征',
    colorPalette: ['按上传图原始配色保留'],
    materials: ['按上传图材质推断，后续由人工确认'],
    accessories: ['按上传图可见配件保留'],
    signatureElements: ['上传图中的主体轮廓', '上传图中的五官组合', '上传图中的核心配件和标识'],
    manufacturingNotes: ['以原图为外观基准，尺寸、材料和工艺待人工确认'],
    patentFocus: ['整体轮廓', '五官组合', '配色关系', '配件造型'],
    marketingAngle: ['复刻原始角色外观', '扩展为专利/生产/宣发素材包'],
    negativePrompt: '不要改变上传图的角色五官、表情、姿态、配色、材质纹理、配件位置和品牌符号',
    sourceImageUrl: imageUrl,
    lockedAt: now,
  };
}

async function imageUrlToVisionInput(url: string): Promise<{ image_url: string } | null> {
  const local = /^\/api\/img\//.test(url) || /^data:/i.test(url);
  if (!local && /^https?:\/\//i.test(url)) return { image_url: url };

  const image = await readImageUrl(url);
  if (image.type.includes('svg')) return null;
  return {
    image_url: `data:${image.type};base64,${image.buf.toString('base64')}`,
  };
}

export async function inferCharacterSpecFromImage(opts: {
  imageUrl: string;
  userHint?: string;
  fallback?: CharacterSpec;
}): Promise<CharacterSpec> {
  const fallback = opts.fallback ?? genericCharacterSpec(opts.imageUrl, opts.userHint);
  const key = process.env.OPENAI_API_KEY;
  if (!key) return fallback;

  const imageInput = await imageUrlToVisionInput(opts.imageUrl);
  if (!imageInput) return fallback;

  const prompt = [
    '你是玩具产品经理、外观专利素材规划师和工厂打样顾问。',
    '根据上传图片推断 CharacterSpec，严格输出 JSON，不要 markdown，不要解释。',
    '字段必须完整匹配：name, oneLiner, targetUser, speciesShape, bodyRatio, faceFeatures, colorPalette, materials, accessories, signatureElements, manufacturingNotes, patentFocus, marketingAngle, negativePrompt, sourceImageId, sourceImageUrl, lockedAt。',
    '数组字段必须为字符串数组。',
    '如果图像中存在用户上传的原创品牌名、角色名、服装字样、帽标、面罩图案或配件图形，必须作为角色识别元素保留，不要改名、翻译、替换或弱化。',
    '不要把已知第三方商业 IP 当成可用授权素材；只有图像明显疑似迪士尼、三丽鸥、泡泡玛特等已注册第三方 IP 时，才在 negativePrompt 中提示替换为原创元素。',
    'negativePrompt 只能禁止无关品牌、水印、真实明星肖像、第三方已注册 IP 和偏离源图的元素；不要要求替换用户上传图中的原创品牌/主标识。',
    opts.userHint?.trim() ? `用户提示：${opts.userHint.trim()}` : '用户没有提供命名提示，请根据图像生成一个中性原创名称。',
    `当前时间戳：${Date.now()}`,
    `源图 URL：${opts.imageUrl}`,
    `兜底 JSON：${JSON.stringify(fallback)}`,
  ].join('\n');

  const res = await fetch(`${GPT_API_BASE}/responses`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${key}`,
    },
    body: JSON.stringify({
      model: GPT_TEXT_MODEL,
      input: [
        {
          role: 'user',
          content: [
            { type: 'input_text', text: prompt },
            { type: 'input_image', image_url: imageInput.image_url },
          ],
        },
      ],
      text: { format: { type: 'json_object' } },
    }),
  });
  if (!res.ok) throw new Error(`GPT vision ${res.status}: ${await res.text()}`);
  const data = await res.json() as {
    output_text?: string;
    output?: Array<{ content?: Array<{ text?: string }> }>;
  };
  const text = readResponseText(data);
  return text.trim() ? JSON.parse(text) as CharacterSpec : fallback;
}
