const crypto = require('crypto');
const DatabaseClient = require('../storage/db');
const config = require('../../config/default');

class SqliteQueue {
  constructor() {
    this.db = new DatabaseClient(config.db);
  }

  _serial(payload) {
    return typeof payload === 'string' ? payload : JSON.stringify(payload);
  }

  async insertRaw(connector, payload) {
    const content = this._serial(payload);
    return this.db.run(
      `INSERT INTO inbox_raw (connector, payload) VALUES (?, ?)`,
      [connector, content]
    );
  }

  async markRawParsed(id, status, error) {
    await this.db.run(
      `UPDATE inbox_raw SET status = ?, parse_status = ?, error = ? WHERE id = ?`,
      [status, error ? 'error' : 'ok', error, id]
    );
  }

  async insertOutbox(canonical, dedupeKey) {
    const payload = this._serial(canonical);
    try {
      return await this.db.run(
        `INSERT INTO outbox_result (canonical_payload, status, dedupe_key, next_attempt_at) VALUES (?, 'pending', ?, 0)`,
        [payload, dedupeKey]
      );
    } catch (err) {
      if (err.message && err.message.includes('UNIQUE constraint failed')) {
        const existing = await this.db.get(`SELECT * FROM outbox_result WHERE dedupe_key = ?`, [dedupeKey]);
        return { existing, duplicate: true };
      }
      throw err;
    }
  }

  async fetchPending(batchSize) {
    const now = Math.floor(Date.now() / 1000);
    return this.db.all(
      `SELECT * FROM outbox_result WHERE status IN ('pending','retrying') AND next_attempt_at <= ? ORDER BY next_attempt_at ASC LIMIT ?`,
      [now, batchSize]
    );
  }

  async claimPending(batchSize, workerId) {
    const now = Math.floor(Date.now() / 1000);
    const candidates = await this.db.all(
      `SELECT id FROM outbox_result WHERE status IN ('pending','retrying') AND next_attempt_at <= ? ORDER BY next_attempt_at ASC LIMIT ?`,
      [now, batchSize * 2]
    );
    const locked = [];
    const stale = now - (config.worker.lockTTLSeconds || 60);
    for (const candidate of candidates) {
      const result = await this.db.run(
        `UPDATE outbox_result SET locked_at = ?, locked_by = ? WHERE id = ? AND (locked_at IS NULL OR locked_at <= ?)`,
        [now, workerId, candidate.id, stale]
      );
      if (result.changes === 1) {
        const entry = await this.db.get(`SELECT * FROM outbox_result WHERE id = ?`, [candidate.id]);
        locked.push(entry);
        if (locked.length >= batchSize) break;
      }
    }
    return locked;
  }

  async markOutboxStatus(id, status, { lastError = null, attempts = null, nextAttemptAt = null } = {}) {
    const fields = ['status = ?'];
    const params = [status];
    if (lastError !== null) {
      fields.push('last_error = ?');
      params.push(lastError);
    }
    if (attempts !== null) {
      fields.push('attempts = ?');
      params.push(attempts);
    }
    if (nextAttemptAt !== null) {
      fields.push('next_attempt_at = ?');
      params.push(nextAttemptAt);
    }
    fields.push('locked_at = NULL', 'locked_by = NULL');
    params.push(id);
    await this.db.run(`UPDATE outbox_result SET ${fields.join(', ')} WHERE id = ?`, params);
  }

  async recordDeliveryAttempt({ outboxId, attempt, status, responseCode, responseBody, latency }) {
    await this.db.run(
      `INSERT INTO delivery_log (outbox_id, attempt, status, response_code, response_body, latency_ms) VALUES (?, ?, ?, ?, ?, ?)` ,
      [outboxId, attempt, status, responseCode, responseBody, latency]
    );
  }

  async moveToDeadLetter(payload, reason) {
    await this.db.run(
      `INSERT INTO dead_letter (payload, reason) VALUES (?, ?)` ,
      [this._serial(payload), reason]
    );
  }

  async pendingCount() {
    const row = await this.db.get(`SELECT COUNT(*) as count FROM outbox_result WHERE status = 'pending'`);
    return row ? row.count : 0;
  }

  async retryingCount() {
    const row = await this.db.get(`SELECT COUNT(*) as count FROM outbox_result WHERE status = 'retrying'`);
    return row ? row.count : 0;
  }

  async deadLetterCount() {
    const row = await this.db.get(`SELECT COUNT(*) as count FROM dead_letter`);
    return row ? row.count : 0;
  }

  async getLastSuccessTimestamp() {
    const row = await this.db.get(
      `SELECT MAX(created_at) as last_success FROM delivery_log WHERE status = 'success'`
    );
    return row?.last_success || null;
  }

  async getAverageLatency() {
    const row = await this.db.get(`SELECT AVG(latency_ms) as avg_latency FROM delivery_log WHERE latency_ms IS NOT NULL`);
    return row?.avg_latency || 0;
  }

  async getDeliveryAttempts() {
    const row = await this.db.get(`SELECT COUNT(*) as total FROM delivery_log`);
    return row ? row.total : 0;
  }

  async ping() {
    await this.db.get('SELECT 1 as ok');
    return true;
  }

  async close() {
    await this.db.close();
  }
}

module.exports = new SqliteQueue();
