const fs = require('node:fs');

const POLICY_LABELS = [
  '商业险保单号',
  '保险单号',
  '保险单号码',
  '电子保单号',
  '保单号',
  '保险合同号',
  '合同号',
];

const VIN_LABELS = ['车辆识别代号', '车辆识别代码', '车架号', 'VIN码', 'VIN', '识别代码'];
const USAGE_NATURE_LABELS = ['使用性质', '车辆使用性质', '使用方式', '使用用途', '车辆用途'];
const USAGE_NATURE_VALUES = [
  '家庭自用汽车',
  '家庭自用',
  '非营业个人',
  '非营业',
  '非营运',
  '企业非营业',
  '党政机关事业团体非营业',
  '预约出租客运',
  '网约车',
  '出租',
  '租赁',
  '客运',
  '货运',
  '营业',
  '营运',
];
const INSURANCE_PERIOD_LABELS = [
  '保险期间',
  '保险期限',
  '保险有效期',
  '保险起期',
  '保险止期',
  '起保日期',
  '终止日期',
  '起止日期',
  '承保期间',
];
const NAME_LABELS = [
  { label: '被保险人', terms: ['被保险人名称', '被保险人姓名', '被保险人'] },
  { label: '车主', terms: ['车主姓名', '车主名称', '车主'] },
  { label: '投保人', terms: ['投保人名称', '投保人姓名', '投保人'] },
];
const COVERAGE_TERMS = ['机动车损失保险', '车辆损失保险', '车损险'];

function toHalfWidth(text) {
  return String(text || '')
    .normalize('NFKC')
    .replace(/\u3000/g, ' ')
    .replace(/[：﹕]/g, ':')
    .replace(/[，、；;]/g, ' ')
    .replace(/[（(].*?[）)]/g, match => match);
}

function normalizeToken(value) {
  return String(value || '')
    .normalize('NFKC')
    .replace(/[^A-Za-z0-9-]/g, '')
    .toUpperCase();
}

function compactText(value) {
  return String(value || '').normalize('NFKC').replace(/\s+/g, '').toUpperCase();
}

function normalizeName(value) {
  return String(value || '').normalize('NFKC').replace(/\s+/g, '').trim();
}

function normalizeDate(year, month, day) {
  return [
    String(year).padStart(4, '0'),
    String(month).padStart(2, '0'),
    String(day).padStart(2, '0'),
  ].join('-');
}

function readInputText(input) {
  if (typeof input.rawText === 'string' && input.rawText.trim()) return input.rawText;
  if (Array.isArray(input.lines)) return input.lines.filter(Boolean).join('\n');
  return '';
}

function linesFromText(text) {
  return toHalfWidth(text)
    .split(/\r?\n+/)
    .map(line => line.trim())
    .filter(Boolean);
}

function hasAnyLabel(line, labels) {
  return labels.some(label => line.includes(label));
}

function extractCandidates(text) {
  const result = [];
  const re = /[A-Z0-9][A-Z0-9-]{7,49}/gi;
  let match;
  while ((match = re.exec(toHalfWidth(text).toUpperCase())) !== null) {
    result.push(normalizeToken(match[0]));
  }
  return [...new Set(result)];
}

function isVin(value) {
  const token = normalizeToken(value).replace(/-/g, '');
  return /^[A-HJ-NPR-Z0-9]{17}$/.test(token);
}

function isIdCard(value) {
  return /^\d{17}[\dX]$/i.test(String(value || '').trim());
}

function isDateLike(value) {
  return /^\d{4}[-/.]\d{1,2}[-/.]\d{1,2}$/.test(String(value || ''));
}

function isValidPolicyNo(value, options = {}) {
  const token = normalizeToken(value);
  if (token.length < 8 || token.length > 50) return false;
  if (!/^[A-Z0-9-]+$/.test(token)) return false;
  const expectedVin = normalizeToken(options.expectedVin).replace(/-/g, '');
  if (isVin(token.replace(/-/g, ''))) {
    if (!options.allowVinShape || token.replace(/-/g, '') === expectedVin) return false;
  }
  if (isIdCard(token) || isDateLike(token)) return false;
  const digits = (token.match(/\d/g) || []).length;
  const letters = (token.match(/[A-Z]/g) || []).length;
  if (digits < 4) return false;
  if (letters === 0 && token.length === 11) return false;
  return letters > 0 || token.length >= 12;
}

function scorePolicyNo(value) {
  const token = normalizeToken(value);
  let score = token.length;
  if (/[A-Z]/.test(token)) score += 8;
  if (/\d/.test(token)) score += 4;
  if (token.includes('-')) score -= 2;
  if (/^P[A-Z0-9]/.test(token)) score += 3;
  return score;
}

function bestPolicyCandidate(candidates, expectedPolicyNo, options = {}) {
  const expected = normalizeToken(expectedPolicyNo);
  if (expected) {
    const exact = candidates.map(normalizeToken).find(item => item === expected);
    if (exact) return exact;
  }
  const valid = candidates
    .map(normalizeToken)
    .filter(item => isValidPolicyNo(item, options));
  if (!valid.length) return '';
  return valid.sort((a, b) => scorePolicyNo(b) - scorePolicyNo(a))[0] || '';
}

function extractPolicyNo(lines, fullText, expectedPolicyNo, expectedVin) {
  for (let i = 0; i < lines.length; i += 1) {
    if (!hasAnyLabel(lines[i], POLICY_LABELS)) continue;
    const windowText = [lines[i], lines[i + 1] || '', lines[i + 2] || ''].join(' ');
    const candidate = bestPolicyCandidate(extractCandidates(windowText), expectedPolicyNo, {
      allowVinShape: true,
      expectedVin,
    });
    if (candidate) return candidate;
  }
  return bestPolicyCandidate(extractCandidates(fullText), expectedPolicyNo, { expectedVin });
}

function extractVin(lines, fullText, expectedVin) {
  const expected = normalizeToken(expectedVin).replace(/-/g, '');
  if (expected && isVin(expected) && compactText(fullText).includes(expected)) return expected;

  for (let i = 0; i < lines.length; i += 1) {
    if (!hasAnyLabel(lines[i], VIN_LABELS)) continue;
    const windowText = [lines[i], lines[i + 1] || ''].join(' ');
    const candidate = extractCandidates(windowText)
      .map(item => item.replace(/-/g, ''))
      .find(isVin);
    if (candidate) return candidate;
  }

  return extractCandidates(fullText)
    .map(item => item.replace(/-/g, ''))
    .find(isVin) || '';
}

function cleanNameText(text) {
  return String(text || '')
    .replace(/^(名称|姓名|客户名称|客户姓名)[:：]?/, '')
    .split(/(?:证件|身份证|统一社会信用|地址|电话|手机|车架|车辆|号牌|保单|保险|发动机|VIN|使用性质|车辆使用性质|使用方式|使用用途|保险期间|保险期限|保险有效期|起保|终止|承保)/)[0]
    .replace(/[0-9A-Za-z_*xX:：/\\-]/g, ' ')
    .trim();
}

function findNamesInText(text) {
  const cleaned = cleanNameText(text);
  const blacklist = new Set([
    '被保险人',
    '投保人',
    '车主姓名',
    '车主名称',
    '车主',
    '姓名',
    '名称',
    '机动车',
    '商业险',
    '保险单',
    '保险人',
    '使用性质',
    '家庭自用汽车',
    '有限公司',
  ]);
  const names = [];
  const re = /[\u4e00-\u9fa5·]{2,12}/g;
  let match;
  while ((match = re.exec(cleaned)) !== null) {
    const name = normalizeName(match[0]);
    if (!blacklist.has(name) && !name.includes('保险') && !name.includes('地址')) {
      names.push(name);
    }
  }
  return names;
}

function addNameCandidate(candidates, label, name) {
  const normalized = normalizeName(name);
  if (!normalized) return;
  if (candidates.some(item => item.label === label && item.name === normalized)) return;
  if (candidates.some(item => item.name === normalized)) {
    const existing = candidates.find(item => item.name === normalized);
    if (existing && !existing.label.includes(label)) existing.label = `${existing.label}/${label}`;
    return;
  }
  candidates.push({ label, name: normalized });
}

function extractNameCandidates(lines, fullText, expectedOwnerName) {
  const candidates = [];
  const compact = compactText(fullText);
  const expected = normalizeName(expectedOwnerName);
  if (expected && compact.includes(compactText(expected))) {
    addNameCandidate(candidates, '全文匹配', expected);
  }

  for (let i = 0; i < lines.length; i += 1) {
    for (const group of NAME_LABELS) {
      const term = group.terms.find(item => lines[i].includes(item));
      if (!term) continue;
      const sameLineTail = lines[i].slice(lines[i].indexOf(term) + term.length).replace(/^[:：\s]+/, '');
      const windowTexts = [sameLineTail, lines[i + 1] || ''];
      for (const text of windowTexts) {
        for (const name of findNamesInText(text)) {
          addNameCandidate(candidates, group.label, name);
        }
      }
    }
  }
  return candidates.slice(0, 8);
}

function extractCertificate(fullText) {
  const normalized = toHalfWidth(fullText).replace(/\s+/g, '');
  const full = normalized.match(/\d{17}[\dXx]/);
  if (full) return { certificateNo: full[0].toUpperCase(), certificateNoMasked: false };

  const masked = normalized.match(/(?:\d{2,8})[*Xx]{4,14}(?:\d{2,6})|[*Xx]{6,16}(?:\d{2,6})/);
  if (masked) return { certificateNo: masked[0].toUpperCase(), certificateNoMasked: true };
  return { certificateNo: '', certificateNoMasked: false };
}

function extractPlateNo(lines, fullText) {
  const plateRe = /[\u4e00-\u9fa5][A-Z][A-Z0-9]{5,7}/g;
  for (const line of lines) {
    if (!/(车牌|号牌|牌照|牌号)/.test(line)) continue;
    const match = toHalfWidth(line).toUpperCase().match(plateRe);
    if (match) return match[0];
  }
  const match = toHalfWidth(fullText).toUpperCase().match(plateRe);
  return match ? match[0] : '';
}

function extractEngineNo(lines) {
  for (let i = 0; i < lines.length; i += 1) {
    if (!/(发动机号|发动机号码|发动机编号)/.test(lines[i])) continue;
    const windowText = [lines[i], lines[i + 1] || ''].join(' ');
    const candidates = extractCandidates(windowText).filter(item => !isVin(item) && !isValidPolicyNo(item));
    const candidate = candidates.find(item => item.length >= 5 && item.length <= 24);
    if (candidate) return candidate;
  }
  return '';
}

function extractDateCandidatesFromText(text) {
  const dates = [];
  const re = /(\d{4})[年\-/.](\d{1,2})[月\-/.](\d{1,2})日?/g;
  let match;
  while ((match = re.exec(toHalfWidth(text))) !== null) {
    const date = normalizeDate(match[1], match[2], match[3]);
    if (!dates.includes(date)) dates.push(date);
  }
  return dates;
}

function extractLabelTail(line, labels) {
  const matchedLabel = labels.find(label => line.includes(label));
  if (!matchedLabel) return '';
  return line.slice(line.indexOf(matchedLabel) + matchedLabel.length).replace(/^[:：\s]+/, '').trim();
}

function extractUsageNatureFromText(text) {
  const normalized = toHalfWidth(text).replace(/\s+/g, '');
  for (const value of USAGE_NATURE_VALUES) {
    if (normalized.includes(value)) return value;
  }
  return '';
}

function extractUsageNature(lines, fullText) {
  for (let i = 0; i < lines.length; i += 1) {
    if (!hasAnyLabel(lines[i], USAGE_NATURE_LABELS)) continue;
    const windowText = [extractLabelTail(lines[i], USAGE_NATURE_LABELS), lines[i + 1] || ''].join(' ');
    const value = extractUsageNatureFromText(windowText);
    if (value) return value;
  }
  return extractUsageNatureFromText(fullText);
}

function extractDates(lines, fullText) {
  for (let i = 0; i < lines.length; i += 1) {
    if (!hasAnyLabel(lines[i], INSURANCE_PERIOD_LABELS)) continue;
    const windowText = [extractLabelTail(lines[i], INSURANCE_PERIOD_LABELS), lines[i + 1] || ''].join(' ');
    const dates = extractDateCandidatesFromText(windowText);
    if (dates.length >= 2) return { startDate: dates[0], endDate: dates[1] };
    if (dates.length === 1) {
      const nextDates = extractDateCandidatesFromText(lines[i + 1] || '');
      if (nextDates.length) return { startDate: dates[0], endDate: nextDates[0] };
    }
  }

  const dates = extractDateCandidatesFromText(fullText);
  return { startDate: dates[0] || '', endDate: dates[1] || '' };
}

function extractCoverage(fullText) {
  const compact = String(fullText || '').replace(/\s+/g, '');
  const evidence = COVERAGE_TERMS.filter(term => compact.includes(term));
  return {
    hasVehicleDamageCoverage: evidence.length > 0,
    coverageEvidence: evidence,
  };
}

function buildWarnings({ input, output }) {
  const warnings = [];
  if (!output.policyNo) warnings.push('未识别到商业险保单号');
  if (!output.vin) warnings.push('未识别到车架号VIN');
  if (!output.nameCandidates.length) warnings.push('未识别到被保险人/车主/投保人姓名');
  if (!output.usageNature) warnings.push('未识别到使用性质');
  if (!output.startDate || !output.endDate) warnings.push('未识别到保险期间');
  if (!output.hasVehicleDamageCoverage) warnings.push('未识别到车损险险种证据');

  const expectedPolicyNo = normalizeToken(input.expectedPolicyNo);
  if (expectedPolicyNo && output.policyNo && expectedPolicyNo !== output.policyNo) {
    warnings.push('OCR保单号与订单已填商业险保单号不一致');
  }
  const expectedVin = normalizeToken(input.expectedVin).replace(/-/g, '');
  if (expectedVin && output.vin && expectedVin !== output.vin) {
    warnings.push('OCR车架号VIN与订单VIN不一致');
  }
  const expectedOwnerName = normalizeName(input.expectedOwnerName);
  if (expectedOwnerName && output.nameCandidates.length) {
    const matched = output.nameCandidates.some(item => normalizeName(item.name) === expectedOwnerName);
    if (!matched) warnings.push('OCR姓名候选与订单车主姓名不一致');
  }
  return warnings;
}

function computeConfidence(output) {
  let score = 0.2;
  if (output.policyNo) score += 0.25;
  if (output.vin) score += 0.25;
  if (output.nameCandidates.length) score += 0.15;
  if (output.usageNature) score += 0.05;
  if (output.startDate && output.endDate) score += 0.05;
  if (output.hasVehicleDamageCoverage) score += 0.1;
  if (output.certificateNo) score += 0.03;
  if (output.plateNo || output.engineNo) score += 0.02;
  return Math.max(0, Math.min(0.99, Number(score.toFixed(2))));
}

function parsePolicy(input) {
  const rawText = readInputText(input);
  if (!rawText.trim()) {
    return {
      success: false,
      policyNo: '',
      vin: '',
      nameCandidates: [],
      certificateNo: '',
      certificateNoMasked: false,
      plateNo: '',
      engineNo: '',
      usageNature: '',
      startDate: '',
      endDate: '',
      hasVehicleDamageCoverage: false,
      coverageEvidence: [],
      confidence: 0,
      warnings: [input.imageUrl ? '当前脚本不直接OCR图片，请先传入rawText' : '缺少rawText或lines'],
      rawText: '',
    };
  }

  const fullText = toHalfWidth(rawText);
  const lines = linesFromText(fullText);
  const certificate = extractCertificate(fullText);
  const usageNature = extractUsageNature(lines, fullText);
  const dates = extractDates(lines, fullText);
  const coverage = extractCoverage(fullText);
  const output = {
    success: true,
    policyNo: extractPolicyNo(lines, fullText, input.expectedPolicyNo, input.expectedVin),
    vin: extractVin(lines, fullText, input.expectedVin),
    nameCandidates: extractNameCandidates(lines, fullText, input.expectedOwnerName),
    ...certificate,
    plateNo: extractPlateNo(lines, fullText),
    engineNo: extractEngineNo(lines),
    usageNature,
    ...dates,
    ...coverage,
    confidence: 0,
    warnings: [],
    rawText: fullText,
  };
  output.warnings = buildWarnings({ input, output });
  output.confidence = computeConfidence(output);
  return output;
}

async function main() {
  try {
    const stdin = fs.readFileSync(0, 'utf8');
    const input = JSON.parse(stdin || '{}');
    process.stdout.write(JSON.stringify(parsePolicy(input)));
  } catch (error) {
    process.stdout.write(JSON.stringify({ success: false, error: error.message }));
    process.exitCode = 0;
  }
}

if (require.main === module) {
  main();
}

module.exports = {
  parsePolicy,
  extractPolicyNo,
  extractVin,
  extractNameCandidates,
};
