import { db } from './db.js';
import { randomToken } from './crypto.js';

export function getUserByUsername(username) {
  return db.prepare('SELECT * FROM users WHERE username = ?').get(username);
}

export function setUserPassword(username, passwordHash) {
  return db.prepare('UPDATE users SET password_hash = ? WHERE username = ?').run(passwordHash, username);
}

export function getUserBySession(sessionId) {
  return db.prepare(`
    SELECT users.id, users.username
    FROM sessions
    JOIN users ON users.id = sessions.user_id
    WHERE sessions.id = ? AND sessions.expires_at > ?
  `).get(sessionId, new Date().toISOString());
}

export function createSession(userId) {
  const now = new Date();
  const expires = new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000);
  const id = `sess_${randomToken()}`;
  db.prepare('INSERT INTO sessions (id, user_id, expires_at, created_at) VALUES (?, ?, ?, ?)')
    .run(id, userId, expires.toISOString(), now.toISOString());
  return { id, expiresAt: expires };
}

export function deleteSession(sessionId) {
  db.prepare('DELETE FROM sessions WHERE id = ?').run(sessionId);
}

export function listNodes() {
  return db.prepare('SELECT * FROM nodes ORDER BY name ASC').all().map(formatNode);
}

export function getNode(id) {
  const row = db.prepare('SELECT * FROM nodes WHERE id = ?').get(id);
  return row ? formatNode(row) : null;
}

export function createNode(values) {
  const now = new Date().toISOString();
  const id = `node_${randomToken(8)}`;
  db.prepare(`
    INSERT INTO nodes (id, name, base_url, token, enabled, created_at, updated_at)
    VALUES (?, ?, ?, ?, ?, ?, ?)
  `).run(id, values.name, normalizeBaseUrl(values.baseUrl), values.token, values.enabled ? 1 : 0, now, now);
  return getNode(id);
}

export function updateNode(id, values) {
  const current = getNode(id);
  if (!current) return null;
  const next = {
    name: values.name ?? current.name,
    baseUrl: values.baseUrl ? normalizeBaseUrl(values.baseUrl) : current.baseUrl,
    token: values.token ?? current.token,
    enabled: values.enabled ?? current.enabled,
  };
  db.prepare('UPDATE nodes SET name = ?, base_url = ?, token = ?, enabled = ?, updated_at = ? WHERE id = ?')
    .run(next.name, next.baseUrl, next.token, next.enabled ? 1 : 0, new Date().toISOString(), id);
  return getNode(id);
}

export function deleteNode(id) {
  db.prepare('DELETE FROM nodes WHERE id = ?').run(id);
}

export function createAuditEvent({ user = null, action, targetType, targetId = null, details = {} }) {
  db.prepare(`
    INSERT INTO audit_events (id, user_id, username, action, target_type, target_id, details, created_at)
    VALUES (?, ?, ?, ?, ?, ?, ?, ?)
  `).run(
    `audit_${randomToken(8)}`,
    user?.id || null,
    user?.username || null,
    action,
    targetType,
    targetId,
    JSON.stringify(details),
    new Date().toISOString(),
  );
}

export function listAuditEvents(limit = 100) {
  return db.prepare('SELECT * FROM audit_events ORDER BY created_at DESC LIMIT ?').all(limit).map((row) => ({
    id: row.id,
    userId: row.user_id,
    username: row.username,
    action: row.action,
    targetType: row.target_type,
    targetId: row.target_id,
    details: JSON.parse(row.details || '{}'),
    createdAt: row.created_at,
  }));
}

export function createJobHistory({ node, vmName, type, agentJobId = null, status = 'started', error = null }) {
  const now = new Date().toISOString();
  const id = `hist_${randomToken(8)}`;
  db.prepare(`
    INSERT INTO job_history (id, node_id, node_name, vm_name, type, agent_job_id, status, error, started_at, updated_at)
    VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
  `).run(id, node.id, node.name, vmName, type, agentJobId, status, error, now, now);
  return id;
}

export function updateJobHistory(id, values) {
  const current = db.prepare('SELECT * FROM job_history WHERE id = ?').get(id);
  if (!current) return;
  const finishedAt = values.finishedAt ?? current.finished_at;
  const durationMs = finishedAt && current.started_at ? new Date(finishedAt).getTime() - new Date(current.started_at).getTime() : current.duration_ms;
  db.prepare(`
    UPDATE job_history
    SET status = ?, error = ?, current_step = ?, snapshot_id = ?, finished_at = ?, duration_ms = ?, updated_at = ?
    WHERE id = ?
  `).run(
    values.status ?? current.status,
    values.error ?? current.error,
    values.currentStep ?? current.current_step,
    values.snapshotId ?? current.snapshot_id,
    finishedAt,
    Number.isFinite(durationMs) ? durationMs : null,
    new Date().toISOString(),
    id,
  );
}

export function listOpenJobHistory() {
  return db.prepare(`
    SELECT job_history.*, nodes.base_url, nodes.token
    FROM job_history
    JOIN nodes ON nodes.id = job_history.node_id
    WHERE job_history.agent_job_id IS NOT NULL
      AND job_history.status IN ('accepted', 'queued', 'running')
      AND nodes.enabled = 1
    ORDER BY job_history.started_at ASC
  `).all();
}

export function listJobHistory(limit = 100) {
  return db.prepare('SELECT * FROM job_history ORDER BY started_at DESC LIMIT ?').all(limit).map((row) => ({
    id: row.id,
    nodeId: row.node_id,
    nodeName: row.node_name,
    vmName: row.vm_name,
    type: row.type,
    agentJobId: row.agent_job_id,
    status: row.status,
    error: row.error,
    currentStep: row.current_step,
    snapshotId: row.snapshot_id,
    startedAt: row.started_at,
    finishedAt: row.finished_at,
    durationMs: row.duration_ms,
    updatedAt: row.updated_at,
  }));
}

export function recordNodeHealth(id, status) {
  db.prepare('UPDATE nodes SET last_health_status = ?, last_health_at = ? WHERE id = ?')
    .run(status, new Date().toISOString(), id);
}

export function listSchedules() {
  return db.prepare(`
    SELECT schedules.*, nodes.name AS node_name
    FROM schedules
    JOIN nodes ON nodes.id = schedules.node_id
    ORDER BY node_name ASC, vm_name ASC
  `).all().map(formatSchedule);
}

export function replaceSchedules(schedules) {
  const now = new Date().toISOString();
  try {
    db.exec('BEGIN');
    db.prepare('DELETE FROM schedules').run();
    const insert = db.prepare(`
      INSERT INTO schedules
        (id, node_id, vm_name, enabled, interval_hours, time_of_day, next_run_at, last_run_at, last_error, created_at, updated_at)
      VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
    `);
    for (const row of schedules) {
      insert.run(
        row.id || `sched_${randomToken(8)}`,
        row.nodeId,
        row.vmName,
        row.enabled ? 1 : 0,
        Math.max(1, Number(row.intervalHours) || 24),
        normalizeTimeOfDay(row.timeOfDay),
        row.nextRunAt || nextRunFrom(new Date(), Math.max(1, Number(row.intervalHours) || 24), normalizeTimeOfDay(row.timeOfDay)),
        row.lastRunAt || null,
        row.lastError || '',
        now,
        now,
      );
    }
    db.exec('COMMIT');
  } catch (error) {
    db.exec('ROLLBACK');
    throw error;
  }
  return listSchedules();
}

export function listDueSchedules(date = new Date()) {
  return db.prepare(`
    SELECT schedules.*, nodes.name AS node_name, nodes.base_url, nodes.token, nodes.enabled AS node_enabled
    FROM schedules
    JOIN nodes ON nodes.id = schedules.node_id
    WHERE schedules.enabled = 1 AND nodes.enabled = 1 AND schedules.next_run_at <= ?
  `).all(date.toISOString());
}

export function markScheduleRun(id, values) {
  db.prepare('UPDATE schedules SET next_run_at = ?, last_run_at = ?, last_error = ?, updated_at = ? WHERE id = ?')
    .run(values.nextRunAt, values.lastRunAt || null, values.lastError || '', new Date().toISOString(), id);
}

export function nextRunFrom(date, intervalHours, timeOfDay) {
  const next = withTimeOfDay(date, timeOfDay);
  const intervalMs = intervalHours * 60 * 60 * 1000;
  while (next <= date) next.setTime(next.getTime() + intervalMs);
  return next.toISOString();
}

function formatNode(row) {
  return {
    id: row.id,
    name: row.name,
    baseUrl: row.base_url,
    token: row.token,
    enabled: Boolean(row.enabled),
    lastHealthStatus: row.last_health_status,
    lastHealthAt: row.last_health_at,
    createdAt: row.created_at,
    updatedAt: row.updated_at,
  };
}

function formatSchedule(row) {
  return {
    id: row.id,
    nodeId: row.node_id,
    nodeName: row.node_name,
    vmName: row.vm_name,
    enabled: Boolean(row.enabled),
    intervalHours: row.interval_hours,
    timeOfDay: row.time_of_day,
    nextRunAt: row.next_run_at,
    lastRunAt: row.last_run_at,
    lastError: row.last_error || '',
  };
}

function normalizeBaseUrl(value) {
  return String(value || '').replace(/\/+$/, '');
}

function normalizeTimeOfDay(value) {
  const text = String(value || '02:00');
  return /^\d{2}:\d{2}$/.test(text) ? text : '02:00';
}

function withTimeOfDay(date, timeOfDay) {
  const [hours, minutes] = normalizeTimeOfDay(timeOfDay).split(':').map(Number);
  const next = new Date(date);
  next.setHours(hours, minutes, 0, 0);
  return next;
}
