import { randomBytes } from 'node:crypto';
import type {
  AssetPack,
  CharacterSpec,
  ExportManifest,
  GenImage,
  GenSession,
  PackKind,
  ToyAsset,
} from './types';
import { detectProvider, generateGptImages, generateGptJson, generateMock } from './providers';
import { saveExportManifest, savePackImage } from './storage';
import { FILENAME_SCHEMA, getPackTemplates, PACK_LABELS, renderCharacterSummary, TEMPLATE_FREEZE_VERSION } from './templates';

function slugify(input: string): string {
  const ascii = input
    .trim()
    .toLowerCase()
    .replace(/[^a-z0-9]+/g, '-')
    .replace(/^-+|-+$/g, '');
  return ascii || 'toy';
}

function pickPalette(prompt: string): string[] {
  const colors = ['橙白', '粉白', '蓝白', '黑金', '紫白', '绿色', '米色'];
  const found = colors.filter(c => prompt.includes(c));
  if (found.length > 0) return found;
  return ['主色待定', '辅色待定', '强调色待定'];
}

function buildFallbackCharacterSpec(session: GenSession, sourceImage: GenImage): CharacterSpec {
  const prompt = sourceImage.prompt || session.prompt;
  const name = prompt.includes('AI') ? 'AI 陪伴玩具' : '未命名玩具 IP';
  const materials = prompt.includes('毛绒') ? ['短毛绒/超柔面料', '刺绣五官', 'PP 棉填充'] : ['主体材料待定', '表面工艺待定'];
  const accessories = ['标志性配件待拆解', '吊牌/水洗标预留'];
  if (prompt.includes('机甲')) accessories.unshift('机甲头盔');
  if (prompt.includes('M logo') || prompt.includes('M logo'.toLowerCase())) accessories.unshift('胸前 M 标识');

  return {
    name,
    oneLiner: prompt,
    targetUser: '潮玩/礼品/品牌联名用户',
    speciesShape: prompt.includes('机甲') ? '圆胖机甲陪伴娃娃' : '圆胖毛绒陪伴娃娃',
    bodyRatio: '大头圆胖体型，建议头身比 1:1.2，坐姿或轻微站姿均可打样',
    faceFeatures: '圆润亲和表情，眼睛、嘴巴、腮红需保持与源图一致',
    colorPalette: pickPalette(prompt),
    materials,
    accessories,
    signatureElements: ['圆胖轮廓', '高识别五官', '胸前/身体标志元素', '可被专利图清楚表达的配件轮廓'],
    manufacturingNotes: ['避免过细尖角和过多小件', '配件连接处需预留缝线或固定方式', '刺绣/印花边界需单独放大确认'],
    patentFocus: ['整体轮廓', '头身比例', '五官组合', '配件造型', '正背侧轮廓差异'],
    marketingAngle: ['AI 陪伴感', '治愈礼物属性', '可收藏 IP 角色', '柔软可抱材质'],
    negativePrompt: '不要改变角色五官、主配色、核心配件；不要加入无关品牌、水印、价格和营销贴纸',
    sourceImageId: sourceImage.id,
    sourceImageUrl: sourceImage.url,
    lockedAt: Date.now(),
  };
}

export async function buildCharacterSpec(session: GenSession, sourceImage: GenImage): Promise<CharacterSpec> {
  const fallback = buildFallbackCharacterSpec(session, sourceImage);
  return generateGptJson<CharacterSpec>({
    fallback,
    prompt: [
      '你是资深毛绒玩具产品经理、外观专利素材规划师和工厂打样顾问。',
      '请根据用户 prompt 与选中图片 URL，输出严格 JSON。不要 markdown，不要解释。',
      '字段必须完整匹配：name, oneLiner, targetUser, speciesShape, bodyRatio, faceFeatures, colorPalette, materials, accessories, signatureElements, manufacturingNotes, patentFocus, marketingAngle, negativePrompt, sourceImageId, sourceImageUrl, lockedAt。',
      '数组字段必须为字符串数组。lockedAt 使用给定时间戳。',
      `当前时间戳：${Date.now()}`,
      `用户 prompt：${session.prompt}`,
      `源图 ID：${sourceImage.id}`,
      `源图 URL：${sourceImage.url}`,
      `兜底 JSON：${JSON.stringify(fallback)}`,
    ].join('\n'),
  });
}

function renderPrompt(template: string, spec: CharacterSpec, sourceImageUrl: string): string {
  return [
    template.replace('{character}', renderCharacterSummary(spec)),
    `参考源图：${sourceImageUrl}`,
    '必须保持同一角色身份、五官、配色、主体比例和核心配件一致。',
  ].join('\n');
}

async function generateAssetImage(opts: {
  packId: string;
  assetId: string;
  prompt: string;
  sourceImageUrl: string;
}): Promise<{ url: string; provider: 'mock' | 'gpt'; raw?: unknown }> {
  const provider = detectProvider();
  const images = provider === 'gpt'
    ? await generateGptImages({
      sessionId: `${opts.packId}_${opts.assetId}`,
      prompt: opts.prompt,
      count: 1,
      refImages: opts.sourceImageUrl.startsWith('http') ? [opts.sourceImageUrl] : [],
    })
    : await generateMock({
      sessionId: `${opts.packId}_${opts.assetId}`,
      prompt: opts.prompt,
      count: 1,
    });

  const image = images[0];
  if (!image) throw new Error('pack asset generation failed');
  if (image.url.startsWith('data:')) {
    return { url: await savePackImage(opts.packId, opts.assetId, image.url), provider, raw: image.meta };
  }
  return { url: image.url, provider, raw: image.meta };
}

function buildFilename(opts: {
  sessionId: string;
  characterName: string;
  kind: PackKind;
  view: string;
  version: string;
  url: string;
}): string {
  const extMatch = opts.url.match(/\.([a-z0-9]+)(?:\?|$)/i);
  const ext = extMatch?.[1] || 'png';
  return `${opts.sessionId}_${slugify(opts.characterName)}_${opts.kind}_${opts.view}_${opts.version}.${ext}`;
}

export async function generateAssetPack(opts: {
  session: GenSession;
  sourceImage: GenImage;
  kind: PackKind;
}): Promise<{ pack: AssetPack; manifest: ExportManifest; provider: 'mock' | 'gpt' }> {
  const templates = getPackTemplates(opts.kind);
  const characterSpec = opts.session.characterSpec?.sourceImageId === opts.sourceImage.id
    ? opts.session.characterSpec
    : await buildCharacterSpec(opts.session, opts.sourceImage);
  const version = 'v01';
  const packId = `pack_${opts.kind}_${Date.now().toString(36)}_${randomBytes(3).toString('hex')}`;
  const createdAt = Date.now();
  const provider = detectProvider();

  const assets: ToyAsset[] = [];
  for (const template of templates) {
    const assetId = `${opts.kind}_${template.filenamePart}_${randomBytes(3).toString('hex')}`;
    const prompt = renderPrompt(template.promptTemplate, characterSpec, opts.sourceImage.url);
    const generated = await generateAssetImage({
      packId,
      assetId,
      prompt,
      sourceImageUrl: opts.sourceImage.url,
    });
    assets.push({
      id: assetId,
      templateId: template.id,
      kind: opts.kind,
      view: template.view,
      title: template.title,
      description: template.description,
      url: generated.url,
      prompt,
      status: 'draft',
      version,
      aspectRatio: template.aspectRatio,
      required: template.required,
      createdAt: Date.now(),
      meta: {
        provider: generated.provider,
        packLabel: PACK_LABELS[opts.kind],
        templateFreezeVersion: TEMPLATE_FREEZE_VERSION,
        raw: generated.raw,
      },
    });
  }

  const manifestId = `manifest_${packId}`;
  const pack: AssetPack = {
    id: packId,
    kind: opts.kind,
    sessionId: opts.session.id,
    sourceImageId: opts.sourceImage.id,
    sourceImageUrl: opts.sourceImage.url,
    characterSpec,
    assets,
    manifestId,
    createdAt,
    version,
    status: 'complete',
  };

  const manifest: ExportManifest = {
    id: manifestId,
    sessionId: opts.session.id,
    packId,
    packKind: opts.kind,
    version,
    createdAt,
    filenameSchema: FILENAME_SCHEMA,
    templateFreezeVersion: TEMPLATE_FREEZE_VERSION,
    source: {
      prompt: opts.session.prompt,
      sourceImageId: opts.sourceImage.id,
      sourceImageUrl: opts.sourceImage.url,
    },
    characterSpec,
    files: assets.map(asset => {
      const template = templates.find(t => t.id === asset.templateId);
      return {
        assetId: asset.id,
        templateId: asset.templateId,
        filename: buildFilename({
          sessionId: opts.session.id,
          characterName: characterSpec.name,
          kind: opts.kind,
          view: asset.view,
          version: asset.version,
          url: asset.url,
        }),
        url: asset.url,
        kind: asset.kind,
        view: asset.view,
        title: asset.title,
        required: asset.required,
        aspectRatio: asset.aspectRatio,
        prompt: asset.prompt,
        checklist: template?.checklist ?? [],
      };
    }),
    exportTargets: ['zip', 'pdf', 'manifest-json'],
  };

  await saveExportManifest(manifest);
  return { pack, manifest, provider };
}
