const { all, audit, now, one, run } = require('../db');
const { getConfig, sendMail } = require('./notifications');
const { deliverAlerts } = require('./webhooks');
const servicenow = require('./servicenow');
const teams = require('./teams');
const { pmAlertCandidates } = require('./pm');

function todayStr() {
  return new Date().toISOString().slice(0, 10);
}

function daysUntil(dateStr) {
  return Math.round((new Date(`${dateStr}T00:00:00`) - new Date(`${todayStr()}T00:00:00`)) / 86400000);
}

function mk(type, referenceType, referenceId, assetId, severity, title, message, dueDate) {
  return { type, reference_type: referenceType, reference_id: referenceId, asset_id: assetId, severity, title, message, due_date: dueDate };
}

// Compute the current set of due/overdue/expiring items that warrant an alert.
function candidates(leadDays) {
  const today = todayStr();
  const horizon = new Date(Date.now() + leadDays * 86400000).toISOString().slice(0, 10);
  const list = [];

  const tasks = all(
    `SELECT t.*, a.asset_id AS asset_code FROM tasks t LEFT JOIN assets a ON a.id = t.asset_id
     WHERE t.status != 'complete' AND t.due_date IS NOT NULL`
  );
  for (const task of tasks) {
    const where = task.asset_code ? ` on ${task.asset_code}` : '';
    if (task.due_date < today) {
      list.push(mk('maintenance_overdue', 'task', task.id, task.asset_id, 'critical', `Overdue: ${task.title}`, `"${task.title}"${where} was due ${task.due_date}.`, task.due_date));
    } else if (task.due_date <= horizon) {
      list.push(mk('maintenance_due', 'task', task.id, task.asset_id, 'warning', `Upcoming: ${task.title}`, `"${task.title}"${where} is due ${task.due_date}.`, task.due_date));
    }
  }

  const warranties = all(
    `SELECT w.*, a.asset_id AS asset_code FROM warranties w LEFT JOIN assets a ON a.id = w.asset_id
     WHERE w.expiration_date IS NOT NULL`
  );
  for (const w of warranties) {
    const label = w.provider || 'Warranty';
    const where = w.asset_code ? ` on ${w.asset_code}` : '';
    if (w.expiration_date < today) {
      list.push(mk('warranty_expired', 'warranty', w.id, w.asset_id, 'critical', 'Warranty expired', `${label}${where} expired ${w.expiration_date}.`, w.expiration_date));
    } else if (w.expiration_date <= horizon) {
      const sev = daysUntil(w.expiration_date) <= 7 ? 'critical' : 'warning';
      list.push(mk('warranty_expiring', 'warranty', w.id, w.asset_id, sev, 'Warranty expiring', `${label}${where} expires ${w.expiration_date}.`, w.expiration_date));
    }
  }

  const leases = all(
    `SELECT l.*, a.asset_id AS asset_code FROM leases l LEFT JOIN assets a ON a.id = l.asset_id
     WHERE l.end_date IS NOT NULL`
  );
  for (const l of leases) {
    if (l.end_date < today || l.end_date > horizon) continue;
    const where = l.asset_code ? ` on ${l.asset_code}` : '';
    const sev = daysUntil(l.end_date) <= 7 ? 'critical' : 'warning';
    list.push(mk('lease_expiring', 'lease', l.id, l.asset_id, sev, 'Lease ending', `Lease with ${l.lessor || 'lessor'}${where} ends ${l.end_date}.`, l.end_date));
  }

  list.push(...pmAlertCandidates());

  return list;
}

// Reconcile computed candidates against stored alerts: create new, refresh existing,
// resolve ones that no longer apply, and respect dismissals.
function scanAlerts(userId) {
  const cfg = getConfig();
  const ts = now();
  const cands = candidates(cfg.leadDays);
  const seen = new Set();
  const created = [];

  for (const c of cands) {
    seen.add(`${c.reference_type}:${c.reference_id}:${c.type}`);
    // Every candidate derives from an asset; the alert inherits that asset's company for filtering.
    const entityId = c.asset_id ? (one('SELECT entity_id FROM assets WHERE id = ?', [c.asset_id])?.entity_id ?? null) : null;
    const existing = one('SELECT * FROM alerts WHERE reference_type = ? AND reference_id = ? AND type = ?', [c.reference_type, c.reference_id, c.type]);
    if (existing) {
      if (existing.status === 'dismissed') continue;
      run(
        `UPDATE alerts SET asset_id = ?, entity_id = ?, severity = ?, title = ?, message = ?, due_date = ?,
           status = CASE WHEN status = 'resolved' THEN 'open' ELSE status END, updated_at = ?
         WHERE id = ?`,
        [c.asset_id, entityId, c.severity, c.title, c.message, c.due_date, ts, existing.id]
      );
    } else {
      const result = run(
        `INSERT INTO alerts (type, reference_type, reference_id, asset_id, entity_id, severity, title, message, due_date, status, created_at, updated_at)
         VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'open', ?, ?)`,
        [c.type, c.reference_type, c.reference_id, c.asset_id, entityId, c.severity, c.title, c.message, c.due_date, ts, ts]
      );
      created.push(one('SELECT * FROM alerts WHERE id = ?', [result.lastInsertRowid]));
    }
  }

  for (const alert of all("SELECT * FROM alerts WHERE status IN ('open', 'acknowledged')")) {
    if (!seen.has(`${alert.reference_type}:${alert.reference_id}:${alert.type}`)) {
      run("UPDATE alerts SET status = 'resolved', updated_at = ? WHERE id = ?", [ts, alert.id]);
    }
  }

  if (userId) audit(userId, 'scan', 'alerts', null, null, { created: created.length });
  return { created, openCount: one("SELECT COUNT(*) AS c FROM alerts WHERE status = 'open'").c };
}

function listAlerts(query = {}) {
  return all(
    `SELECT al.*, a.asset_id AS asset_code, a.description AS asset_description, u.name AS acknowledged_by_name
     FROM alerts al
     LEFT JOIN assets a ON a.id = al.asset_id
     LEFT JOIN users u ON u.id = al.acknowledged_by
     WHERE (? IS NULL OR al.status = ?) AND (? IS NULL OR al.type = ?) AND (? IS NULL OR al.entity_id = ?)
     ORDER BY CASE al.severity WHEN 'critical' THEN 0 WHEN 'warning' THEN 1 ELSE 2 END, al.due_date IS NULL, al.due_date`,
    [query.status || null, query.status || null, query.type || null, query.type || null, query.entity_id || null, query.entity_id || null]
  );
}

function summary(companyId) {
  const rows = all(
    "SELECT status, severity, COUNT(*) AS count FROM alerts WHERE (? IS NULL OR entity_id = ?) GROUP BY status, severity",
    [companyId || null, companyId || null]
  );
  const open = rows.filter((r) => r.status === 'open');
  return {
    open: open.reduce((s, r) => s + r.count, 0),
    critical: open.filter((r) => r.severity === 'critical').reduce((s, r) => s + r.count, 0),
    acknowledged: rows.filter((r) => r.status === 'acknowledged').reduce((s, r) => s + r.count, 0)
  };
}

function setStatus(id, status, userId) {
  const before = one('SELECT * FROM alerts WHERE id = ?', [id]);
  if (!before) return null;
  run(
    'UPDATE alerts SET status = ?, acknowledged_by = ?, acknowledged_at = ?, updated_at = ? WHERE id = ?',
    [
      status,
      status === 'acknowledged' ? userId : before.acknowledged_by,
      status === 'acknowledged' ? now() : before.acknowledged_at,
      now(),
      id
    ]
  );
  audit(userId, status, 'alert', id, before, { status });
  return one('SELECT * FROM alerts WHERE id = ?', [id]);
}

function digestHtml(alerts) {
  const rows = alerts.map((a) =>
    `<tr><td style="padding:6px 10px;color:${a.severity === 'critical' ? '#b3261e' : '#a56a00'};font-weight:700">${a.severity.toUpperCase()}</td>` +
    `<td style="padding:6px 10px">${a.title}</td><td style="padding:6px 10px">${a.message || ''}</td>` +
    `<td style="padding:6px 10px">${a.due_date || ''}</td></tr>`
  ).join('');
  return `<h2>DepreCore alerts</h2><p>${alerts.length} item(s) need attention.</p>` +
    `<table style="border-collapse:collapse;font-family:Arial,sans-serif;font-size:13px">` +
    `<thead><tr><th align="left" style="padding:6px 10px">Severity</th><th align="left" style="padding:6px 10px">Alert</th>` +
    `<th align="left" style="padding:6px 10px">Detail</th><th align="left" style="padding:6px 10px">Due</th></tr></thead><tbody>${rows}</tbody></table>`;
}

function digestText(alerts) {
  return `DepreCore alerts (${alerts.length}):\n` +
    alerts.map((a) => `- [${a.severity}] ${a.title} — ${a.message || ''} ${a.due_date ? `(due ${a.due_date})` : ''}`).join('\n');
}

// Scan, then notify on newly raised, not-yet-notified open alerts: an email digest
// (when SMTP is enabled) and/or a per-alert JSON webhook POST (when enabled). An alert
// is marked notified once any channel has handled it, so it is delivered at most once.
async function runAlertCycle(userId) {
  const { created } = scanAlerts(userId);
  const cfg = getConfig();
  const result = { created: created.length, emailed: false, webhooked: false };

  const snCfg = servicenow.getConfig();
  const teamsCfg = teams.getConfig();
  const emailReady = cfg.enabled && cfg.host && cfg.recipients.length;
  const webhookReady = cfg.webhookEnabled && cfg.webhookUrl;
  const ticketReady = snCfg.ticketEnabled && snCfg.autoTicket && snCfg.instanceUrl;
  const teamsReady = teamsCfg.enabled && teamsCfg.webhookUrl;
  if (!emailReady && !webhookReady && !ticketReady && !teamsReady) return result;

  const pending = all(
    `SELECT al.*, a.asset_id AS asset_code
     FROM alerts al LEFT JOIN assets a ON a.id = al.asset_id
     WHERE al.status = 'open' AND al.notified_at IS NULL`
  );
  if (!pending.length) return result;

  if (emailReady) {
    await sendMail({
      subject: `DepreCore: ${pending.length} new alert(s)`,
      html: digestHtml(pending),
      text: digestText(pending)
    });
    result.emailed = true;
    result.count = pending.length;
  }

  if (webhookReady) {
    const delivery = await deliverAlerts(pending);
    result.webhooked = delivery.attempted;
    result.delivered = delivery.delivered;
    result.failed = delivery.failed;
  }

  if (ticketReady) {
    const tickets = await servicenow.autoTicketAlerts(pending, userId);
    result.ticketed = tickets.created;
  }

  if (teamsReady) {
    const teamsDelivery = await teams.deliverAlerts(pending);
    result.teamsPosted = teamsDelivery.attempted;
    result.teamsDelivered = teamsDelivery.delivered;
    result.teamsFailed = teamsDelivery.failed;
  }

  const ts = now();
  for (const alert of pending) run('UPDATE alerts SET notified_at = ? WHERE id = ?', [ts, alert.id]);
  return result;
}

module.exports = { listAlerts, runAlertCycle, scanAlerts, setStatus, summary };
