const A4 = { width: 2480, height: 3508 };

function roundRect(ctx, x, y, width, height, radius = 18) {
  ctx.beginPath();
  ctx.roundRect(x, y, width, height, radius);
}

function fitText(ctx, text, maxWidth, initialSize, weight = 400) {
  let size = initialSize;
  do {
    ctx.font = `${weight} ${size}px "Microsoft YaHei", "PingFang SC", sans-serif`;
    if (ctx.measureText(String(text)).width <= maxWidth) return size;
    size -= 2;
  } while (size > 24);
  return size;
}

function drawText(ctx, text, x, y, { size = 36, weight = 400, color = '#17213f', align = 'left', maxWidth } = {}) {
  if (maxWidth) size = fitText(ctx, text, maxWidth, size, weight);
  ctx.font = `${weight} ${size}px "Microsoft YaHei", "PingFang SC", sans-serif`;
  ctx.fillStyle = color;
  ctx.textAlign = align;
  ctx.textBaseline = 'alphabetic';
  ctx.fillText(String(text ?? ''), x, y, maxWidth);
}

function jpegPdf(dataUrl, width, height) {
  const binary = atob(dataUrl.split(',')[1]);
  const image = Uint8Array.from(binary, char => char.charCodeAt(0));
  const encoder = new TextEncoder();
  const chunks = [];
  const offsets = [0];
  let length = 0;
  const add = value => { const bytes = typeof value === 'string' ? encoder.encode(value) : value; chunks.push(bytes); length += bytes.length; };
  add('%PDF-1.4\n%\xE2\xE3\xCF\xD3\n');
  const object = (id, body) => { offsets[id] = length; add(`${id} 0 obj\n${body}\nendobj\n`); };
  object(1, '<< /Type /Catalog /Pages 2 0 R >>');
  object(2, '<< /Type /Pages /Kids [3 0 R] /Count 1 >>');
  object(3, '<< /Type /Page /Parent 2 0 R /MediaBox [0 0 595.28 841.89] /Resources << /XObject << /Im0 4 0 R >> >> /Contents 5 0 R >>');
  offsets[4] = length;
  add(`4 0 obj\n<< /Type /XObject /Subtype /Image /Width ${width} /Height ${height} /ColorSpace /DeviceRGB /BitsPerComponent 8 /Filter /DCTDecode /Length ${image.length} >>\nstream\n`);
  add(image); add('\nendstream\nendobj\n');
  const stream = 'q\n595.28 0 0 841.89 0 0 cm\n/Im0 Do\nQ';
  object(5, `<< /Length ${stream.length} >>\nstream\n${stream}\nendstream`);
  const xref = length;
  add(`xref\n0 6\n0000000000 65535 f \n`);
  for (let id = 1; id <= 5; id += 1) add(`${String(offsets[id]).padStart(10, '0')} 00000 n \n`);
  add(`trailer\n<< /Size 6 /Root 1 0 R >>\nstartxref\n${xref}\n%%EOF`);
  const output = new Uint8Array(length);
  let cursor = 0;
  for (const chunk of chunks) { output.set(chunk, cursor); cursor += chunk.length; }
  return new Blob([output], { type: 'application/pdf' });
}

function downloadCanvasPdf(canvas, filename) {
  const blob = jpegPdf(canvas.toDataURL('image/jpeg', .94), canvas.width, canvas.height);
  const link = document.createElement('a');
  link.href = URL.createObjectURL(blob);
  link.download = filename.replace(/[\\/:*?"<>|]/g, '-');
  link.click();
  setTimeout(() => URL.revokeObjectURL(link.href), 3000);
}

function loadImage(source) {
  return new Promise((resolve, reject) => {
    const image = new Image();
    image.onload = () => resolve(image);
    image.onerror = () => reject(new Error('防伪二维码加载失败'));
    image.src = source;
  });
}

async function drawQrCode(ctx, dataUrl, x, y, size) {
  if (!dataUrl) return;
  const image = await loadImage(dataUrl);
  ctx.fillStyle = '#ffffff';
  ctx.fillRect(x - 10, y - 10, size + 20, size + 20);
  ctx.drawImage(image, x, y, size, size);
}

export async function downloadScoreReport({ organization, candidate, exam, results, summary, verificationCode, verificationUrl, verificationQr }) {
  const canvas = document.createElement('canvas');
  Object.assign(canvas, A4);
  const ctx = canvas.getContext('2d');
  ctx.fillStyle = '#f5f8fb'; ctx.fillRect(0, 0, canvas.width, canvas.height);
  ctx.fillStyle = '#14234b'; ctx.fillRect(0, 0, canvas.width, 270);
  ctx.fillStyle = '#c94b45'; ctx.fillRect(170, 234, 250, 12);
  drawText(ctx, organization?.name || '考试服务平台', 170, 112, { size: 42, weight: 700, color: '#ffffff' });
  drawText(ctx, '考 生 成 绩 单', 170, 205, { size: 76, weight: 700, color: '#ffffff' });
  drawText(ctx, exam.code, 2300, 115, { size: 32, weight: 600, color: '#9eadce', align: 'right' });
  drawText(ctx, exam.name, 2300, 192, { size: 38, weight: 500, color: '#ffffff', align: 'right', maxWidth: 1120 });

  const box = (x, y, w, h, fill = '#ffffff') => { roundRect(ctx, x, y, w, h, 22); ctx.fillStyle = fill; ctx.fill(); ctx.strokeStyle = '#dce4ec'; ctx.lineWidth = 2; ctx.stroke(); };
  box(170, 330, 2140, 300);
  const meta = [['姓名', candidate.name], ['报名号', candidate.candidateNumber], ['考试', exam.name], ['发布时间', summary?.publishedAt ? new Date(summary.publishedAt).toLocaleString('zh-CN') : '以系统记录为准']];
  meta.forEach(([label, value], index) => {
    const x = 225 + (index % 2) * 1050, y = 420 + Math.floor(index / 2) * 115;
    drawText(ctx, label, x, y, { size: 28, color: '#77839a' });
    drawText(ctx, value, x + 155, y, { size: 34, weight: 600, maxWidth: 800 });
  });
  box(170, 690, 2140, 320, '#eaf4f1');
  const totals = [['总分', `${summary?.total ?? '—'} / ${summary?.fullScore ?? '—'}`], ['特征分', summary?.featureScore ?? 0], ['合格结论', summary?.qualified == null ? '不判定' : summary.qualified ? '合格' : '未合格'], ['发布进度', `${summary?.publishedSubjects ?? results.length} / ${summary?.subjectCount ?? results.length} 科`]];
  totals.forEach(([label, value], index) => {
    const x = 235 + index * 520;
    drawText(ctx, label, x, 790, { size: 28, color: '#5d766f' });
    drawText(ctx, value, x, 900, { size: 48, weight: 700, color: '#173b35', maxWidth: 440 });
  });

  drawText(ctx, '科目成绩与等级排名', 170, 1115, { size: 42, weight: 700 });
  drawText(ctx, '等级与排名均以系统正式发布数据为准', 2310, 1115, { size: 25, color: '#7b8598', align: 'right' });
  const cols = 2, gap = 34, cardW = (2140 - gap) / cols, cardH = Math.min(300, Math.max(220, (1760 - Math.ceil(results.length / cols) * 20) / Math.ceil(results.length / cols)));
  results.forEach((item, index) => {
    const col = index % cols, row = Math.floor(index / cols), x = 170 + col * (cardW + gap), y = 1180 + row * (cardH + 20);
    box(x, y, cardW, cardH);
    drawText(ctx, item.subjectName, x + 42, y + 72, { size: 38, weight: 700, maxWidth: cardW - 450 });
    drawText(ctx, item.qualified == null ? '不判定' : item.qualified ? '达线' : '未达线', x + cardW - 42, y + 70, { size: 27, weight: 600, color: item.qualified === false ? '#b43d38' : '#2d7462', align: 'right' });
    drawText(ctx, item.score, x + 42, y + 158, { size: 62, weight: 700 });
    drawText(ctx, `/ ${item.fullScore}`, x + 190, y + 156, { size: 28, color: '#8993a6' });
    drawText(ctx, `${item.grade}  ·  第 ${item.rank} / ${item.cohortSize} 名  ·  前 ${item.rankPercent}%`, x + 42, y + 220, { size: 28, color: '#455068', maxWidth: cardW - 84 });
    drawText(ctx, item.passText || '不设单科线', x + 42, y + cardH - 30, { size: 24, color: '#7c8798', maxWidth: cardW - 84 });
  });

  const footerY = 3100;
  box(170, footerY, 2140, 235, '#f0f3f7');
  drawText(ctx, '防伪查询码', 225, footerY + 70, { size: 28, color: '#6f7a8e' });
  drawText(ctx, verificationCode, 225, footerY + 135, { size: 38, weight: 700, color: '#17213f' });
  drawText(ctx, '登录考试服务平台，在“文书防伪查询”中输入本码核验。', 225, footerY + 188, { size: 24, color: '#667085' });
  drawText(ctx, verificationUrl, 2035, footerY + 135, { size: 21, color: '#53627b', align: 'right', maxWidth: 820 });
  await drawQrCode(ctx, verificationQr, 2075, footerY + 26, 180);
  drawText(ctx, `生成时间 ${new Date().toLocaleString('zh-CN')}`, 2310, 3435, { size: 22, color: '#8a94a6', align: 'right' });
  downloadCanvasPdf(canvas, `${exam.name}-${candidate.name}-成绩单.pdf`);
}

export async function downloadAdmissionNotice({ organization, candidate, exam, placement, school, template, verificationCode, verificationUrl, verificationQr, noticeNumber }) {
  const canvas = document.createElement('canvas'); Object.assign(canvas, A4);
  const ctx = canvas.getContext('2d');
  const primary = template.primaryColor || '#8d2028', accent = template.accentColor || '#c9a45b';
  ctx.fillStyle = '#fffdf8'; ctx.fillRect(0, 0, canvas.width, canvas.height);
  ctx.strokeStyle = primary; ctx.lineWidth = 10; ctx.strokeRect(90, 90, 2300, 3328);
  ctx.strokeStyle = accent; ctx.lineWidth = 3; ctx.strokeRect(120, 120, 2240, 3268);
  drawText(ctx, template.eyebrow || 'ADMISSION NOTICE', 1240, 350, { size: 30, weight: 600, color: accent, align: 'center' });
  drawText(ctx, template.title || '录 取 通 知 书', 1240, 560, { size: 96, weight: 700, color: primary, align: 'center', maxWidth: 1950 });
  drawText(ctx, school.name, 1240, 700, { size: 42, weight: 600, align: 'center', maxWidth: 1900 });
  drawText(ctx, `通知书编号：${noticeNumber || placement.payload.noticeNumber || '—'}`, 2180, 835, { size: 27, weight: 600, color: '#655d53', align: 'right', maxWidth: 1250 });
  drawText(ctx, `${candidate.name} 同学：`, 300, 1040, { size: 48, weight: 700 });
  const body = (template.body || '经审核，你已被我校 {{录取类别}} 正式录取。谨向你表示祝贺！请按学校通知要求办理报到手续。')
    .replaceAll('{{考生姓名}}', candidate.name).replaceAll('{{考试名称}}', exam.name).replaceAll('{{录取学校}}', school.name).replaceAll('{{录取类别}}', placement.payload.categoryName || '招生类别');
  const lines = [];
  for (const paragraph of body.split(/\n+/)) {
    let line = '';
    for (const char of paragraph) {
      ctx.font = '400 42px "Microsoft YaHei", sans-serif';
      if (ctx.measureText(line + char).width > 1840) { lines.push(line); line = char; } else line += char;
    }
    if (line) lines.push(line); lines.push('');
  }
  lines.slice(0, 13).forEach((line, index) => drawText(ctx, line, 320, 1210 + index * 82, { size: 42, color: '#332f2c' }));
  drawText(ctx, template.footer || '请妥善保管本通知书，报到时出示。', 300, 2550, { size: 32, color: '#6c6257', maxWidth: 1700 });
  drawText(ctx, school.name, 2080, 2750, { size: 38, weight: 700, color: primary, align: 'right' });
  drawText(ctx, new Date().toLocaleDateString('zh-CN'), 2080, 2820, { size: 30, color: '#5f5951', align: 'right' });
  roundRect(ctx, 240, 3040, 2000, 210, 20); ctx.fillStyle = '#f4efe5'; ctx.fill();
  drawText(ctx, '防伪查询码', 300, 3115, { size: 26, color: '#756b5e' });
  drawText(ctx, verificationCode, 300, 3185, { size: 35, weight: 700 });
  drawText(ctx, verificationUrl, 1940, 3185, { size: 20, color: '#71695f', align: 'right', maxWidth: 820 });
  await drawQrCode(ctx, verificationQr, 1995, 3055, 175);
  drawText(ctx, organization?.name || '考试服务平台', 1240, 3380, { size: 23, color: '#8a8177', align: 'center' });
  downloadCanvasPdf(canvas, `${school.name}-${candidate.name}-录取通知书.pdf`);
}
