import fs from 'node:fs';
import { Resvg } from '@resvg/resvg-js';
import JSZip from 'jszip';
import type { BuiltExportPayload } from '../../../api/src/features/tenant-content/exports.js';
import { config } from '../config.js';

interface RenderDailyPracticePackageResult {
  body: Buffer;
  mimeType: string;
  extension: 'zip';
}

type ExportQuestion = BuiltExportPayload['questions'][number];

const CARD_SIZE = 1080;
const COLLAGE_GAP = 28;
const COLLAGE_PADDING = 42;
const CARD_SLOTS = [0, 1, 2, 3, 5, 6, 7, 8];
const FONT_FAMILY = '"Microsoft YaHei", "Noto Sans CJK SC", "Noto Sans SC", sans-serif';

const THEMES: Record<string, {
  name: string;
  background: string;
  card: string;
  border: string;
  text: string;
  muted: string;
  accent: string;
  accentSoft: string;
  center: string;
  centerText: string;
}> = {
  default: {
    name: 'default',
    background: '#f6f8fb',
    card: '#ffffff',
    border: '#d7dee8',
    text: '#0f172a',
    muted: '#64748b',
    accent: '#0f766e',
    accentSoft: '#dff7f1',
    center: '#0f172a',
    centerText: '#ffffff',
  },
  cream: {
    name: 'cream',
    background: '#fbf7ef',
    card: '#fffdf8',
    border: '#e4d6c4',
    text: '#1f2933',
    muted: '#786a5c',
    accent: '#b45309',
    accentSoft: '#f7ead8',
    center: '#3f2f24',
    centerText: '#fff8ed',
  },
  ink: {
    name: 'ink',
    background: '#111827',
    card: '#f8fafc',
    border: '#334155',
    text: '#0f172a',
    muted: '#475569',
    accent: '#2563eb',
    accentSoft: '#dbeafe',
    center: '#020617',
    centerText: '#e2e8f0',
  },
};

function boolOption(payload: BuiltExportPayload, key: string) {
  return payload.options && typeof payload.options[key] === 'boolean' ? payload.options[key] === true : false;
}

function stringValue(value: unknown, fallback = '') {
  return typeof value === 'string' && value.trim() ? value.trim() : fallback;
}

function plainText(value: unknown) {
  return String(value ?? '')
    .replace(/!\[([^\]]*)\]\(([^)]+)\)/g, ' [图片] ')
    .replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$1')
    .replace(/<br\s*\/?>/gi, '\n')
    .replace(/<\/(p|div|li|tr|h[1-6])>/gi, '\n')
    .replace(/<[^>]+>/g, '')
    .replace(/&nbsp;/g, ' ')
    .replace(/&lt;/g, '<')
    .replace(/&gt;/g, '>')
    .replace(/&amp;/g, '&')
    .replace(/\r\n/g, '\n')
    .replace(/\n{3,}/g, '\n\n')
    .trim();
}

function xmlEscape(value: unknown) {
  return String(value ?? '')
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&apos;');
}

function estimateWidth(char: string) {
  if (/[\u4e00-\u9fff\u3000-\u303f\uff00-\uffef]/.test(char)) return 1;
  if (/[A-Z0-9]/.test(char)) return 0.68;
  if (/[a-z]/.test(char)) return 0.56;
  if (/\s/.test(char)) return 0.35;
  return 0.5;
}

function wrapText(value: unknown, maxUnits: number, maxLines: number) {
  const normalized = plainText(value).replace(/\s+/g, ' ');
  const lines: string[] = [];
  let line = '';
  let units = 0;
  for (const char of normalized) {
    const width = estimateWidth(char);
    if (units + width > maxUnits && line) {
      lines.push(line.trim());
      line = '';
      units = 0;
      if (lines.length >= maxLines) break;
    }
    line += char;
    units += width;
  }
  if (line && lines.length < maxLines) lines.push(line.trim());
  if (lines.length > 0 && normalized.length > lines.join('').length) {
    lines[lines.length - 1] = `${lines[lines.length - 1].replace(/[。；，、,.!?！？;:：]*$/, '')}...`;
  }
  return lines.filter(Boolean);
}

function textLines(input: {
  lines: string[];
  x: number;
  y: number;
  fontSize: number;
  fill: string;
  lineHeight?: number;
  weight?: number;
  anchor?: 'start' | 'middle';
}) {
  const lineHeight = input.lineHeight || Math.round(input.fontSize * 1.42);
  return input.lines.map((line, index) => (
    `<text x="${input.x}" y="${input.y + index * lineHeight}" font-family="${xmlEscape(FONT_FAMILY)}" font-size="${input.fontSize}" font-weight="${input.weight || 400}" fill="${input.fill}" text-anchor="${input.anchor || 'start'}">${xmlEscape(line)}</text>`
  )).join('\n');
}

function optionText(option: unknown, index: number) {
  const prefix = `${String.fromCharCode(65 + index)}. `;
  if (option && typeof option === 'object' && !Array.isArray(option)) {
    const object = option as Record<string, unknown>;
    return prefix + plainText(object.text ?? object.label ?? object.content ?? JSON.stringify(object));
  }
  return prefix + plainText(option);
}

function answerText(question: ExportQuestion) {
  const answers: string[] = [];
  if (typeof question.correctOptionIndex === 'number') {
    answers.push(String.fromCharCode(65 + question.correctOptionIndex));
  }
  if (Array.isArray(question.correctOptionIndices) && question.correctOptionIndices.length) {
    answers.push(question.correctOptionIndices.map(item => String.fromCharCode(65 + Number(item))).join(', '));
  }
  if (typeof question.answerText === 'string' && question.answerText.trim()) {
    answers.push(plainText(question.answerText));
  }
  return Array.from(new Set(answers.filter(Boolean))).join('；');
}

function subQuestionPreview(question: ExportQuestion) {
  const subQuestions = Array.isArray(question.subQuestions) ? question.subQuestions : [];
  return subQuestions
    .slice(0, 2)
    .map((item, index) => {
      if (!item || typeof item !== 'object' || Array.isArray(item)) return '';
      const object = item as Record<string, unknown>;
      return `(${index + 1}) ${plainText(object.content ?? object.stem ?? object.title ?? '')}`;
    })
    .filter(Boolean)
    .join(' ');
}

function questionTitle(question: ExportQuestion, fallback: string) {
  return stringValue(question.subjectName, '') || stringValue(question.categoryName, '') || fallback;
}

function themeFor(payload: BuiltExportPayload) {
  const key = payload.dailyPractice?.theme || stringValue(payload.options?.theme, 'default');
  return THEMES[key] || THEMES.default;
}

function questionCardSvg(payload: BuiltExportPayload, question: ExportQuestion, index: number) {
  const theme = themeFor(payload);
  const includeAnswers = boolOption(payload, 'includeAnswers');
  const includeExplanations = boolOption(payload, 'includeExplanations');
  const title = questionTitle(question, payload.dailyPractice?.issue || payload.title || '每日一练');
  const type = stringValue(question.typeLabel, stringValue(question.type, '题目'));
  const stem = plainText(question.content);
  const subPreview = subQuestionPreview(question);
  const options = Array.isArray(question.options) ? question.options.slice(0, 4).map(optionText) : [];
  const answer = includeAnswers ? answerText(question) : '';
  const explanation = includeExplanations ? plainText(question.explanation).slice(0, 90) : '';
  const contentLines = wrapText(subPreview ? `${stem} ${subPreview}` : stem, 21, options.length ? 8 : 11);
  const optionLines = options.flatMap(option => wrapText(option, 27, 1)).slice(0, 4);
  const footerLines = [
    answer ? `答案：${answer}` : '',
    explanation ? `解析：${explanation}` : '',
  ].filter(Boolean);

  return `<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="${CARD_SIZE}" height="${CARD_SIZE}" viewBox="0 0 ${CARD_SIZE} ${CARD_SIZE}">
  <rect width="${CARD_SIZE}" height="${CARD_SIZE}" rx="46" fill="${theme.card}"/>
  <rect x="28" y="28" width="${CARD_SIZE - 56}" height="${CARD_SIZE - 56}" rx="38" fill="none" stroke="${theme.border}" stroke-width="4"/>
  <rect x="64" y="64" width="148" height="52" rx="26" fill="${theme.accentSoft}"/>
  <text x="138" y="99" font-family="${xmlEscape(FONT_FAMILY)}" font-size="26" font-weight="700" fill="${theme.accent}" text-anchor="middle">${String(index + 1).padStart(2, '0')}</text>
  <text x="236" y="101" font-family="${xmlEscape(FONT_FAMILY)}" font-size="30" font-weight="700" fill="${theme.text}">${xmlEscape(type)}</text>
  <text x="64" y="172" font-family="${xmlEscape(FONT_FAMILY)}" font-size="24" font-weight="500" fill="${theme.muted}">${xmlEscape(title)}</text>
  <line x1="64" y1="210" x2="${CARD_SIZE - 64}" y2="210" stroke="${theme.border}" stroke-width="3"/>
  ${textLines({ lines: contentLines, x: 72, y: 282, fontSize: 42, fill: theme.text, lineHeight: 64, weight: 650 })}
  ${optionLines.length ? textLines({ lines: optionLines, x: 80, y: 765, fontSize: 32, fill: theme.muted, lineHeight: 48, weight: 500 }) : ''}
  ${footerLines.length ? `<rect x="64" y="908" width="${CARD_SIZE - 128}" height="102" rx="24" fill="${theme.accentSoft}"/>` : ''}
  ${footerLines.length ? textLines({ lines: footerLines.flatMap(line => wrapText(line, 30, 1)), x: 92, y: 950, fontSize: 26, fill: theme.accent, lineHeight: 38, weight: 650 }) : ''}
  <text x="64" y="1034" font-family="${xmlEscape(FONT_FAMILY)}" font-size="22" fill="${theme.muted}">${xmlEscape(payload.dailyPractice?.brand?.english || 'GONGXUE EDU')}</text>
  <text x="${CARD_SIZE - 64}" y="1034" font-family="${xmlEscape(FONT_FAMILY)}" font-size="22" fill="${theme.muted}" text-anchor="end">${xmlEscape(payload.dailyPractice?.date || payload.exportedAt.slice(0, 10))}</text>
</svg>`;
}

function centerCardSvg(payload: BuiltExportPayload) {
  const theme = themeFor(payload);
  const brand = payload.dailyPractice?.brand;
  const center = payload.dailyPractice?.centerSlot;
  const titleLines = wrapText(center?.title || brand?.name || '恭学教育', 9, 2);
  const subtitleLines = wrapText(center?.subtitle || brand?.ctaLine || '每日一练 · 精选八题 · 稳步上岸', 15, 3);
  const sloganLines = wrapText(brand?.slogan || '专注高职升本', 16, 1);

  return `<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="${CARD_SIZE}" height="${CARD_SIZE}" viewBox="0 0 ${CARD_SIZE} ${CARD_SIZE}">
  <rect width="${CARD_SIZE}" height="${CARD_SIZE}" rx="46" fill="${theme.center}"/>
  <circle cx="540" cy="270" r="118" fill="${theme.accent}" opacity="0.16"/>
  <circle cx="540" cy="270" r="78" fill="${theme.accentSoft}" opacity="0.92"/>
  <text x="540" y="288" font-family="${xmlEscape(FONT_FAMILY)}" font-size="40" font-weight="800" fill="${theme.accent}" text-anchor="middle">每日</text>
  ${textLines({ lines: titleLines, x: 540, y: 482, fontSize: 58, fill: theme.centerText, lineHeight: 76, weight: 800, anchor: 'middle' })}
  ${textLines({ lines: sloganLines, x: 540, y: 646, fontSize: 30, fill: '#cbd5e1', lineHeight: 42, weight: 500, anchor: 'middle' })}
  ${textLines({ lines: subtitleLines, x: 540, y: 746, fontSize: 34, fill: theme.centerText, lineHeight: 50, weight: 700, anchor: 'middle' })}
  <line x1="230" y1="906" x2="850" y2="906" stroke="#94a3b8" stroke-width="2" opacity="0.42"/>
  <text x="540" y="972" font-family="${xmlEscape(FONT_FAMILY)}" font-size="28" fill="#cbd5e1" text-anchor="middle">${xmlEscape(brand?.english || 'GONGXUE EDU')}</text>
</svg>`;
}

function emptyCardSvg(payload: BuiltExportPayload, slot: number) {
  const theme = themeFor(payload);
  const brand = payload.dailyPractice?.brand;
  return `<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="${CARD_SIZE}" height="${CARD_SIZE}" viewBox="0 0 ${CARD_SIZE} ${CARD_SIZE}">
  <rect width="${CARD_SIZE}" height="${CARD_SIZE}" rx="46" fill="${theme.card}"/>
  <rect x="28" y="28" width="${CARD_SIZE - 56}" height="${CARD_SIZE - 56}" rx="38" fill="none" stroke="${theme.border}" stroke-width="4" stroke-dasharray="18 18"/>
  <text x="540" y="478" font-family="${xmlEscape(FONT_FAMILY)}" font-size="44" font-weight="800" fill="${theme.muted}" text-anchor="middle">题位待补</text>
  <text x="540" y="548" font-family="${xmlEscape(FONT_FAMILY)}" font-size="30" fill="${theme.muted}" text-anchor="middle">Slot ${slot + 1}</text>
  <text x="540" y="646" font-family="${xmlEscape(FONT_FAMILY)}" font-size="30" fill="${theme.accent}" text-anchor="middle">${xmlEscape(brand?.ctaLine || '每日一练 · 精选八题 · 稳步上岸')}</text>
</svg>`;
}

function renderSvgToPng(svg: string) {
  const fontPath = [config.exportPdfFontPath, 'C:\\Windows\\Fonts\\msyh.ttc', 'C:\\Windows\\Fonts\\simhei.ttf']
    .filter((candidate): candidate is string => Boolean(candidate) && fs.existsSync(candidate));
  const renderer = new Resvg(svg, {
    fitTo: { mode: 'original' },
    font: {
      loadSystemFonts: true,
      fontFiles: fontPath,
      defaultFontFamily: 'Microsoft YaHei',
    },
  });
  return renderer.render().asPng();
}

function dataUri(buffer: Buffer) {
  return `data:image/png;base64,${buffer.toString('base64')}`;
}

function collageSvg(payload: BuiltExportPayload, pngCards: Buffer[]) {
  const theme = themeFor(payload);
  const width = CARD_SIZE * 3 + COLLAGE_GAP * 2 + COLLAGE_PADDING * 2;
  const height = width;
  const title = payload.dailyPractice?.issue || payload.title || '每日一练';
  const images = pngCards.map((buffer, slot) => {
    const row = Math.floor(slot / 3);
    const col = slot % 3;
    const x = COLLAGE_PADDING + col * (CARD_SIZE + COLLAGE_GAP);
    const y = COLLAGE_PADDING + row * (CARD_SIZE + COLLAGE_GAP);
    return `<image href="${dataUri(buffer)}" x="${x}" y="${y}" width="${CARD_SIZE}" height="${CARD_SIZE}"/>`;
  }).join('\n');
  return `<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}">
  <rect width="${width}" height="${height}" fill="${theme.background}"/>
  ${images}
  <text x="${width - 64}" y="${height - 28}" font-family="${xmlEscape(FONT_FAMILY)}" font-size="24" fill="${theme.muted}" text-anchor="end">${xmlEscape(title)} · ${xmlEscape(payload.dailyPractice?.brand?.name || '恭学教育')}</text>
</svg>`;
}

function manifest(payload: BuiltExportPayload) {
  return {
    version: '1.0',
    generatedBy: 'tiku-saas-export-worker',
    generatedAt: new Date().toISOString(),
    exportJobId: payload.jobId,
    tenantId: payload.tenantId,
    exportType: payload.exportType,
    format: payload.format,
    title: payload.title,
    summary: payload.summary,
    dailyPractice: payload.dailyPractice,
    files: {
      collage: {
        png: 'collage.png',
        svg: 'collage.svg',
      },
      cards: Array.from({ length: 9 }).map((_, index) => ({
        slot: index,
        png: `cards/card-${String(index + 1).padStart(2, '0')}.png`,
        svg: `cards/card-${String(index + 1).padStart(2, '0')}.svg`,
      })),
    },
    safety: {
      includeAnswers: boolOption(payload, 'includeAnswers'),
      includeExplanations: boolOption(payload, 'includeExplanations'),
      note: 'Card files are rendered server-side from the audited export payload. Private media URLs are not embedded.',
    },
  };
}

export async function renderDailyPracticePackage(payload: BuiltExportPayload): Promise<RenderDailyPracticePackageResult> {
  if (payload.exportType !== 'daily_practice') {
    throw new Error('daily_practice_zip renderer only supports daily_practice exports');
  }

  const zip = new JSZip();
  const cardPngs: Buffer[] = [];
  const bySlot = new Map<number, ExportQuestion>();
  payload.questions.slice(0, 8).forEach((question, index) => {
    bySlot.set(CARD_SLOTS[index], question);
  });

  for (let slot = 0; slot < 9; slot += 1) {
    const question = bySlot.get(slot);
    const svg = question ? questionCardSvg(payload, question, CARD_SLOTS.indexOf(slot)) : slot === 4 ? centerCardSvg(payload) : emptyCardSvg(payload, slot);
    const png = renderSvgToPng(svg);
    cardPngs[slot] = png;
    const name = String(slot + 1).padStart(2, '0');
    zip.file(`cards/card-${name}.svg`, svg);
    zip.file(`cards/card-${name}.png`, png);
  }

  const collageSource = collageSvg(payload, cardPngs);
  zip.file('collage.svg', collageSource);
  zip.file('collage.png', renderSvgToPng(collageSource));
  zip.file('manifest.json', JSON.stringify(manifest(payload), null, 2));
  zip.file('payload.json', JSON.stringify(payload, null, 2));

  return {
    body: await zip.generateAsync({
      type: 'nodebuffer',
      compression: 'DEFLATE',
      compressionOptions: { level: 7 },
    }),
    mimeType: 'application/zip',
    extension: 'zip',
  };
}
