// cronJob.js
const cron = require('node-cron');
const axios = require('axios');
const db = require('../models');
const { logger } = require('nodejs-log-agent');
const moment = require('moment');
const { Op } = require('sequelize');
const { SKIPPABLE_ERROR_MESSAGES } = require('../config/constants');
const { sendAutoSkipNotification } = require('../services/emailService');
moment.locale('id');

// ====== ENV / CONFIG ======
const WA_API_URL = process.env.WA_API_URL;
const API_URL = process.env.API_URL;
const WA_ACCESS_TOKEN = process.env.WA_ACCESS_TOKEN;

// Config default dari env, namun dapat di-override dari database settings
let BATCH_SIZE = parseInt(process.env.BATCH_SIZE || '2', 10);
let DELAY_BETWEEN_BATCHES_MS = parseInt(process.env.DELAY_BETWEEN_BATCHES_MS || '20000', 10);
let DELAY_PER_MESSAGE_MS = parseInt(process.env.DELAY_PER_MESSAGE_MS || '5000', 10);

// ====== STATE ======
let isCronScheduledRunning = true;
let desiredCronState = true;
let isExecuting = false;
let shouldStop = false;
let lastRun = null;

// ====== STATISTIK EKSEKUSI ======
let executionHistory = []; // Menyimpan N eksekusi terakhir
const MAX_HISTORY_ENTRIES = 50;

// ====== AMBIL CONFIG DARI DB ======
async function loadConfigFromDB() {
  try {
    const cronSettings = await db.setting.findAll({ where: { group: 'cron' } });
    const cfg = {};
    cronSettings.forEach(s => { cfg[s.key] = s.value; });

    if (cfg.cron_batch_size) BATCH_SIZE = parseInt(cfg.cron_batch_size, 10);
    if (cfg.cron_delay_between_batches_ms) DELAY_BETWEEN_BATCHES_MS = parseInt(cfg.cron_delay_between_batches_ms, 10);
    if (cfg.cron_delay_per_message_ms) DELAY_PER_MESSAGE_MS = parseInt(cfg.cron_delay_per_message_ms, 10);
  } catch (error) {
    logger.warn(`[Cron] Gagal memuat config dari DB, menggunakan default: ${error.message}`);
  }
}

// ====== AUTO-SKIP EXPIRED INVOICES ======
async function autoSkipExpiredInvoices() {
  try {
    // Ambil threshold dari DB settings
    const thresholdSetting = await db.setting.findOne({ where: { key: 'auto_skip_threshold_hours' } });
    const enabledSetting = await db.setting.findOne({ where: { key: 'auto_skip_enabled' } });

    const isEnabled = enabledSetting ? enabledSetting.value === 'true' : true;
    if (!isEnabled) {
      logger.info('[Auto-Skip] Fitur auto-skip dinonaktifkan dari settings.');
      return [];
    }

    const thresholdHours = thresholdSetting ? parseInt(thresholdSetting.value, 10) : 24;
    const cutoff = moment().subtract(thresholdHours, 'hours').toDate();

    // Cari invoice yang pending dan sudah melewati batas waktu
    const expiredInvoices = await db.invoice.findAll({
      where: {
        delivered: false,
        createdAt: { [Op.lt]: cutoff }
      }
    });

    if (expiredInvoices.length === 0) {
      return [];
    }

    const now = new Date();
    const expiredIds = expiredInvoices.map(inv => inv.inv_id);

    // Update status menjadi delivered dengan data skip
    await db.invoice.update(
      {
        delivered: true,
        skipped_at: now,
        skip_reason: 'auto_expired',
        skipped_by: 'system'
      },
      { where: { inv_id: { [Op.in]: expiredIds } } }
    );

    logger.warn(
      `[Auto-Skip] ${expiredInvoices.length} invoice di-skip otomatis (>${thresholdHours} jam): ${expiredIds.join(', ')}`,
      { count: expiredInvoices.length, threshold: thresholdHours, invoiceIds: expiredIds }
    );

    // Kirim notifikasi email (non-blocking)
    sendAutoSkipNotification(expiredInvoices, thresholdHours).catch(err => {
      logger.error(`[Auto-Skip] Gagal kirim email notifikasi: ${err.message}`);
    });

    return expiredInvoices;
  } catch (error) {
    logger.error(`[Auto-Skip] Error saat proses auto-skip: ${error.message}`, {
      message: error.message,
      stack: error.stack
    });
    return [];
  }
}

// ====== CORE CRON TASK ======
const cronTask = async () => {
  if (isExecuting) {
    logger.warn('[Cron Job] Masih berjalan, eksekusi baru di-skip.');
    return;
  }

  isExecuting = true;
  const jobStartTime = new Date();
  lastRun = jobStartTime;
  const executionEntry = {
    startTime: jobStartTime.toISOString(),
    endTime: null,
    duration: null,
    status: 'running',
    sent: 0,
    failed: 0,
    skipped: 0,
    autoSkipped: 0
  };

  logger.info(`[Cron Job] Memulai pengiriman invoice yang belum terkirim.`, { timestamp: jobStartTime.toISOString() });

  try {
    // Muat config terbaru dari DB setiap eksekusi
    await loadConfigFromDB();

    // 1. Jalankan auto-skip lebih dulu
    const autoSkipped = await autoSkipExpiredInvoices();
    executionEntry.autoSkipped = autoSkipped.length;

    // 2. Ambil invoice yang masih pending (sudah dikurangi yang di-skip)
    const pendingInvoices = await getPendingInvoices();

    if (pendingInvoices.length === 0) {
      // logger.info(`[Cron Job] Tidak ada invoice yang belum terkirim.`);
    } else {
      logger.info(`[Cron Job] Ditemukan ${pendingInvoices.length} invoice yang belum terkirim.`, { count: pendingInvoices.length });
      const results = await processMessagesInBatches(pendingInvoices);
      executionEntry.sent = results.sent;
      executionEntry.failed = results.failed;
      executionEntry.skipped = results.skipped;
    }

    const jobEndTime = new Date();
    executionEntry.endTime = jobEndTime.toISOString();
    executionEntry.duration = jobEndTime.getTime() - jobStartTime.getTime();
    executionEntry.status = 'success';

    logger.info(`[Cron Job] Pengiriman invoice selesai. Durasi: ${executionEntry.duration} ms.`, {
      timestamp: jobEndTime.toISOString()
    });
  } catch (error) {
    executionEntry.status = 'error';
    executionEntry.error = error.message;
    executionEntry.endTime = new Date().toISOString();
    executionEntry.duration = new Date().getTime() - jobStartTime.getTime();

    logger.error(`[Cron Job] Gagal memproses pesan invoice: ${error.message}`, {
      message: error.message,
      stack: error.stack,
      context: 'Terjadi kesalahan saat memproses pesan invoice pada cron job utama.'
    });
  } finally {
    isExecuting = false;
    // Simpan ke history (batasi jumlahnya)
    executionHistory.unshift(executionEntry);
    if (executionHistory.length > MAX_HISTORY_ENTRIES) {
      executionHistory = executionHistory.slice(0, MAX_HISTORY_ENTRIES);
    }
  }
};

// ====== SCHEDULER ======
const job = cron.schedule('*/1 * * * *', cronTask, {
  scheduled: true,
});

// ====== PUBLIC CONTROL API ======
const startJob = () => {
  if (!isCronScheduledRunning) {
    job.start();
    isCronScheduledRunning = true;
    logger.info('[Cron] Cron job dimulai');
  }
  desiredCronState = true;
  shouldStop = false;
};

const stopJob = () => {
  if (isCronScheduledRunning) {
    job.stop();
    isCronScheduledRunning = false;
    logger.info('[Cron] Cron job dihentikan');
  }
  desiredCronState = false;
  shouldStop = true;
};

const restartJob = () => {
  desiredCronState = true;
  stopJob();
  startJob();
  logger.info('[Cron] Cron job direstart (scheduler dihentikan dan dimulai kembali).');
};

const pushJob = () => {
  if (isCronScheduledRunning) {
    if (isExecuting) {
      logger.warn('[Cron] Cron job masih berjalan, push di-skip.');
      return;
    }
    shouldStop = false;
    cronTask();
    logger.info('[Cron] Cron job dipush (dijalankan manual sekali).');
  } else {
    logger.warn('[Cron] Cron job tidak dapat dipush karena scheduler tidak aktif.');
  }
};

const getStatus = () => isCronScheduledRunning;
const getDesiredState = () => desiredCronState;
const isTaskRunning = () => isExecuting;
const getExecutionHistory = () => executionHistory;

const getTimestamps = () => {
  const nextRun = lastRun ? new Date(lastRun.getTime() + 60 * 1000) : null;
  return {
    lastRun: lastRun ? moment(lastRun).format('YYYY-MM-DD HH:mm:ss') : null,
    nextRun: nextRun ? moment(nextRun).format('YYYY-MM-DD HH:mm:ss') : null,
  };
};

const getStats = () => {
  const total = executionHistory.length;
  const success = executionHistory.filter(e => e.status === 'success').length;
  const errors = executionHistory.filter(e => e.status === 'error').length;
  const totalSent = executionHistory.reduce((sum, e) => sum + (e.sent || 0), 0);
  const totalFailed = executionHistory.reduce((sum, e) => sum + (e.failed || 0), 0);
  const totalAutoSkipped = executionHistory.reduce((sum, e) => sum + (e.autoSkipped || 0), 0);

  return { total, success, errors, totalSent, totalFailed, totalAutoSkipped };
};

// ====== DB ======
async function getPendingInvoices() {
  try {
    const data = await db.invoice.findAll({
      where: { delivered: false },
    });
    return data;
  } catch (error) {
    logger.error(`[DB] Error mengambil data invoice: ${error.message}`, {
      message: error.message,
      stack: error.stack,
      context: 'Gagal mengambil data invoice dari database.'
    });
    throw error;
  }
}

// ====== BATCH PROCESSOR ======
async function processMessagesInBatches(dataDB) {
  let sentCount = 0;
  let failedCount = 0;
  let skippedCount = 0;
  const totalBatches = Math.ceil(dataDB.length / BATCH_SIZE);

  for (let i = 0; i < dataDB.length; i += BATCH_SIZE) {
    if (shouldStop) {
      logger.warn(`[Batch Processor] Proses dihentikan manual saat batch ke-${Math.floor(i / BATCH_SIZE) + 1}`);
      break;
    }

    const batch = dataDB.slice(i, i + BATCH_SIZE);
    logger.info(`[Batch Processor] Memproses batch ${Math.floor(i / BATCH_SIZE) + 1}/${totalBatches} (${batch.length} pesan).`);

    const batchResults = await sendBatchMessages(batch);
    sentCount += batchResults.sent;
    failedCount += batchResults.failed;
    skippedCount += batchResults.skipped;

    if (i + BATCH_SIZE < dataDB.length) {
      logger.info(`[Batch Processor] Menunggu ${DELAY_BETWEEN_BATCHES_MS / 1000} detik sebelum batch berikutnya...`);
      await delay(DELAY_BETWEEN_BATCHES_MS);
    }
  }

  logger.info(`[Batch Processor] Semua batch telah diproses. Total terkirim: ${sentCount}, Total gagal: ${failedCount}, Total dilewati: ${skippedCount}.`);
  return { sent: sentCount, failed: failedCount, skipped: skippedCount };
}

// ====== SINGLE BATCH SENDER ======
async function sendBatchMessages(batch) {
  let sentInBatch = 0;
  let failedInBatch = 0;
  let skippedInBatch = 0;

  for (const data of batch) {
    try {
      const param = { number: data.phoneNumber };
      let route = '';

      if (!data.isFile) {
        param.message = data.content;
        route = '/api/sendText';
      } else if (!data.pdfCreated) {
        logger.warn(`[Batch] pdf not created for ${data.inv_id} (${data.inv_name}) invoice skipped.`);
        skippedInBatch++;
        continue;
      } else {
        param.fileUrl = `${API_URL}${data.pdfName}`;
        param.caption = data.content;
        route = '/api/sendFile';
      }

      logger.info(`[debug] batch message data : ${JSON.stringify(data)} route : ${route} param : ${JSON.stringify(param)}`);

      const result = await sendMessage(route, param, data.inv_name, data.inv_id);

      if (result) {
        await updateDeliveredStatus(data.inv_id, data.inv_name);
        sentInBatch++;
      }
    } catch (error) {
      logger.error(`[Batch] Gagal memproses pesan untuk invoice ID ${data.inv_id} (${data.inv_name}): ${error.message}`, {
        invoiceId: data.inv_id,
        invoiceName: data.inv_name,
        message: error.message,
        stack: error.stack,
        context: 'Terjadi kesalahan saat memproses satu pesan dalam batch.'
      });
      failedInBatch++;
    }

    await delay(DELAY_PER_MESSAGE_MS);
  }

  logger.info(`[Batch] Batch selesai. Berhasil: ${sentInBatch}, Gagal: ${failedInBatch}, Dilewati: ${skippedInBatch}.`);
  return { sent: sentInBatch, failed: failedInBatch, skipped: skippedInBatch };
}

// ====== SENDER ======
async function sendMessage(route, param, inv_name, inv_id) {
  try {
    const response = await axios.post(WA_API_URL + route, param, {
      timeout: 10000,
      headers: {
        'Authorization': `Bearer ${WA_ACCESS_TOKEN}`
      }
    });

    logger.info(`[Send Message] Pesan berhasil dikirim untuk: ${inv_name} (Tujuan: ${param.number}). Respon: ${JSON.stringify(response.data)}`);
    return true;
  } catch (error) {
    let errorMessage = `Gagal mengirim pesan untuk: ${inv_name} (Tujuan: ${param.number})`;

    logger.error(`[Send Message] ${errorMessage}`, {
      invoiceName: inv_name,
      phoneNumber: param.number,
      message: error.message,
      stack: error.stack,
      axiosError: error.isAxiosError ? {
        code: error.code,
        responseStatus: error.response ? error.response.status : undefined,
        responseData: error.response ? error.response.data : undefined,
      } : undefined,
      context: 'Gagal saat memanggil API pengiriman pesan.'
    });

    if (error.response) {
      errorMessage += `. Status: ${error.response.status}, Data: ${JSON.stringify(error.response.data)}`;
    } else if (error.request) {
      errorMessage += `. Tidak ada respon dari server.`;
    } else {
      errorMessage += `. Error: ${error.message}`;
    }

    const respMsg = error.response?.data?.message;
    const isSkippableError = respMsg && SKIPPABLE_ERROR_MESSAGES.some(skipMsg => respMsg.includes(skipMsg));

    if (isSkippableError) {
      await db.invoice.update(
        {
          delivered: true,
          skipped_at: new Date(),
          skip_reason: 'phone_error',
          skipped_by: 'system'
        },
        { where: { inv_id } }
      );
      logger.warn(`[Send Message] Invoice ditandai 'delivered' karena error yang tidak bisa di-retry: ${errorMessage}`);
      return false;
    }

    throw error;
  }
}

// ====== DB UPDATE ======
async function updateDeliveredStatus(invoiceId, inv_name, retry = 0) {
  try {
    const [updatedRows] = await db.invoice.update(
      { delivered: true },
      { where: { inv_id: invoiceId } }
    );

    if (updatedRows > 0) {
      logger.info(`[DB] Status invoice ${inv_name} (ID: ${invoiceId}) berhasil diperbarui ke 'delivered: true'.`);
    } else {
      logger.warn(`[DB] Invoice ${inv_name} (ID: ${invoiceId}) tidak ditemukan atau sudah 'delivered'. Tidak ada pembaruan.`);
    }
  } catch (error) {
    const isTransient =
      error.message.includes('ECONNRESET') ||
      error.message.includes('ETIMEDOUT') ||
      error.message.includes('timeout') ||
      error.message.includes('SequelizeConnection') ||
      error.code === 'ECONNREFUSED';

    if (retry < 2 && isTransient) {
      logger.warn(`[DB] Retry ke-${retry + 1} update invoice ${inv_name} (ID: ${invoiceId}) karena error sementara: ${error.message}`);
      await delay(1000);
      return updateDeliveredStatus(invoiceId, inv_name, retry + 1);
    }

    logger.error(`[CRITICAL] Gagal update status invoice ${inv_name} (ID: ${invoiceId}) setelah ${retry} percobaan.`, {
      invoiceId,
      invoiceName: inv_name,
      retry,
      message: error.message,
      stack: error.stack,
      context: 'Gagal memperbarui status invoice di database meskipun sudah retry.'
    });
  }
}

// ====== HELPERS ======
function delay(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

// ====== TEST HELPERS ======
function _simulateCrash() {
  if (isCronScheduledRunning) {
    job.stop();
    isCronScheduledRunning = false;
    logger.warn('[TEST] Mensimulasikan cron job berhenti secara tak terduga.');
  } else {
    logger.info('[TEST] Simulasi crash tidak dilakukan karena cron job sudah berhenti.');
  }
}

// ====== EXPORTS ======
module.exports = {
  start: startJob,
  stop: stopJob,
  restart: restartJob,
  push: pushJob,
  getStatus,
  getDesiredState,
  getTimestamps,
  isTaskRunning,
  getExecutionHistory,
  getStats,
  autoSkipExpiredInvoices, // Exported untuk unit test
  _simulateCrash,
};
