var zlib = require('zlib');

function sanitizeType(t) {
  return t === 'pager' ? 'pager' : 'extension';
}

function clip(s, n) {
  return String(s == null ? '' : s).trim().substring(0, n || 200);
}

function normalizeExportItem(raw) {
  return {
    location: clip(raw && raw.location, 120),
    name: clip(raw && raw.name, 120),
    number: clip(raw && raw.number, 40),
    type: sanitizeType(raw && raw.type),
    notes: clip(raw && raw.notes, 500)
  };
}

function isValidImportItem(item) {
  return !!(item && item.location && item.name && item.number);
}

function itemKey(item) {
  return [item.location, item.name, item.number, item.type, item.notes].map(function(v) {
    return String(v || '').trim().toLowerCase();
  }).join('\u001f');
}

function looseKeys(item) {
  var location = String(item && item.location || '').trim().toLowerCase();
  var number = String(item && item.number || '').trim().toLowerCase();
  var type = sanitizeType(item && item.type);
  var keys = [];
  if (location && number) keys.push('location-number:' + location + '\u001f' + number);
  if (number && type) keys.push('number-type:' + number + '\u001f' + type);
  return keys;
}

function exportPayload(rows) {
  return {
    format: 'pedscribe-phone-directory',
    version: 1,
    exported_at: new Date().toISOString(),
    items: (rows || []).map(normalizeExportItem).filter(isValidImportItem)
  };
}

function importItemsFromBody(body) {
  var rawItems = Array.isArray(body) ? body : (body && Array.isArray(body.items) ? body.items : []);
  var seen = Object.create(null);
  return rawItems.slice(0, 1000).map(normalizeExportItem).filter(isValidImportItem).filter(function(item) {
    var key = itemKey(item);
    if (seen[key]) return false;
    seen[key] = true;
    return true;
  });
}

function analyzeImport(items, existingRows) {
  var exact = Object.create(null);
  var loose = Object.create(null);
  (existingRows || []).forEach(function(row) {
    var normalized = normalizeExportItem(row);
    var existing = {
      id: row.id,
      location: normalized.location,
      name: normalized.name,
      number: normalized.number,
      type: normalized.type,
      notes: normalized.notes,
      trashed: row.trashed_at != null
    };
    exact[itemKey(normalized)] = existing;
    looseKeys(normalized).forEach(function(key) {
      if (!loose[key]) loose[key] = [];
      loose[key].push(existing);
    });
  });

  var summary = { total: items.length, new: 0, exactActive: 0, exactTrashed: 0, possible: 0 };
  var entries = items.map(function(item, index) {
    var exactMatch = exact[itemKey(item)];
    var possibleMap = Object.create(null);
    looseKeys(item).forEach(function(key) {
      (loose[key] || []).forEach(function(match) { possibleMap[String(match.id)] = match; });
    });
    var possibleMatches = Object.keys(possibleMap).map(function(id) { return possibleMap[id]; });
    var status = 'new';
    if (exactMatch && exactMatch.trashed) status = 'exact_trashed';
    else if (exactMatch) status = 'exact_active';
    else if (possibleMatches.length) status = 'possible_duplicate';

    if (status === 'exact_active') summary.exactActive++;
    else if (status === 'exact_trashed') summary.exactTrashed++;
    else if (status === 'possible_duplicate') summary.possible++;
    else summary.new++;

    var entry = { index: index, status: status, item: item, exactMatch: exactMatch || null, possibleMatches: possibleMatches.slice(0, 5) };

    var importedCandidate = {
      id: 'import-' + index,
      location: item.location,
      name: item.name,
      number: item.number,
      type: item.type,
      notes: item.notes,
      trashed: false,
      incoming: true
    };
    looseKeys(item).forEach(function(key) {
      if (!loose[key]) loose[key] = [];
      loose[key].push(importedCandidate);
    });

    return entry;
  });

  return { summary: summary, entries: entries };
}

function crc32(buf) {
  var table = crc32.table;
  if (!table) {
    table = crc32.table = [];
    for (var i = 0; i < 256; i++) {
      var c = i;
      for (var j = 0; j < 8; j++) c = (c & 1) ? (0xedb88320 ^ (c >>> 1)) : (c >>> 1);
      table[i] = c >>> 0;
    }
  }
  var crc = 0xffffffff;
  for (var k = 0; k < buf.length; k++) crc = table[(crc ^ buf[k]) & 0xff] ^ (crc >>> 8);
  return (crc ^ 0xffffffff) >>> 0;
}

function dosDateTime(date) {
  var d = date || new Date();
  var time = (d.getHours() << 11) | (d.getMinutes() << 5) | Math.floor(d.getSeconds() / 2);
  var day = Math.max(1, d.getDate());
  var month = d.getMonth() + 1;
  var year = Math.max(1980, d.getFullYear()) - 1980;
  return { time: time, date: (year << 9) | (month << 5) | day };
}

function createJsonZip(filename, payload) {
  var name = Buffer.from(filename || 'pedscribe-extensions.json');
  var data = Buffer.from(JSON.stringify(payload, null, 2));
  var stamp = dosDateTime(new Date());
  var crc = crc32(data);
  var local = Buffer.alloc(30);
  local.writeUInt32LE(0x04034b50, 0);
  local.writeUInt16LE(20, 4);
  local.writeUInt16LE(0, 6);
  local.writeUInt16LE(0, 8);
  local.writeUInt16LE(stamp.time, 10);
  local.writeUInt16LE(stamp.date, 12);
  local.writeUInt32LE(crc, 14);
  local.writeUInt32LE(data.length, 18);
  local.writeUInt32LE(data.length, 22);
  local.writeUInt16LE(name.length, 26);
  local.writeUInt16LE(0, 28);

  var central = Buffer.alloc(46);
  central.writeUInt32LE(0x02014b50, 0);
  central.writeUInt16LE(20, 4);
  central.writeUInt16LE(20, 6);
  central.writeUInt16LE(0, 8);
  central.writeUInt16LE(0, 10);
  central.writeUInt16LE(stamp.time, 12);
  central.writeUInt16LE(stamp.date, 14);
  central.writeUInt32LE(crc, 16);
  central.writeUInt32LE(data.length, 20);
  central.writeUInt32LE(data.length, 24);
  central.writeUInt16LE(name.length, 28);
  central.writeUInt16LE(0, 30);
  central.writeUInt16LE(0, 32);
  central.writeUInt16LE(0, 34);
  central.writeUInt16LE(0, 36);
  central.writeUInt32LE(0, 38);
  central.writeUInt32LE(0, 42);

  var centralOffset = local.length + name.length + data.length;
  var centralSize = central.length + name.length;
  var end = Buffer.alloc(22);
  end.writeUInt32LE(0x06054b50, 0);
  end.writeUInt16LE(0, 4);
  end.writeUInt16LE(0, 6);
  end.writeUInt16LE(1, 8);
  end.writeUInt16LE(1, 10);
  end.writeUInt32LE(centralSize, 12);
  end.writeUInt32LE(centralOffset, 16);
  end.writeUInt16LE(0, 20);

  return Buffer.concat([local, name, data, central, name, end]);
}

function parseImportBuffer(buffer) {
  var buf = Buffer.isBuffer(buffer) ? buffer : Buffer.from(buffer || '');
  if (buf.length >= 4 && buf.readUInt32LE(0) === 0x04034b50) {
    if (buf.length < 30) throw new Error('Invalid zip file');
    var method = buf.readUInt16LE(8);
    var compressedSize = buf.readUInt32LE(18);
    var nameLength = buf.readUInt16LE(26);
    var extraLength = buf.readUInt16LE(28);
    var dataStart = 30 + nameLength + extraLength;
    var dataEnd = dataStart + compressedSize;
    if (dataEnd > buf.length) throw new Error('Invalid zip file');
    var data = buf.slice(dataStart, dataEnd);
    if (method === 8) data = zlib.inflateRawSync(data);
    if (method !== 0 && method !== 8) throw new Error('Unsupported zip compression');
    return JSON.parse(data.toString('utf8'));
  }
  return JSON.parse(buf.toString('utf8'));
}

module.exports = {
  sanitizeType,
  clip,
  normalizeExportItem,
  isValidImportItem,
  itemKey,
  looseKeys,
  exportPayload,
  importItemsFromBody,
  analyzeImport,
  createJsonZip,
  parseImportBuffer
};
