/**
 * ApsGo Railway Worker
 * Background service untuk automation scheduling 24/7
 * Features:
 * - Waktu Mode: Scheduled watering by time
 * - Sensor Mode: Automatic watering by soil moisture threshold
 * - Redis Queue: Prevent race conditions & concurrent task management
 * - Firebase Realtime DB: Sync dengan Flutter app dan ESP32
 * - History Monitoring dan History Penyiraman dengan penanda jenis_histori
 */

// ==================== SUPPRESS FIREBASE WARNINGS ====================

process.env.FIREBASE_DATABASE_EMULATOR_HOST = undefined;
process.env.FIRESTORE_EMULATOR_HOST = undefined;

const originalWarn = console.warn;
console.warn = function (...args) {
  const message = args.join(' ');

  if (
    message.includes('FIREBASE WARNING') ||
    message.includes('@firebase/database') ||
    message.includes('firebase/database')
  ) {
    return;
  }

  originalWarn.apply(console, args);
};

// ==================== IMPORTS ====================

require('dotenv').config();

const admin = require('firebase-admin');
const { Queue, Worker } = require('bullmq');
const Redis = require('ioredis');
const cron = require('cron');

// ==================== CONFIGURATION ====================

process.env.TZ = process.env.TZ || 'Asia/Jakarta';

const FIREBASE_PATHS = {
  kontrol: 'kontrol_1',
  aktuator: 'aktuator',
  data: 'data',
  history: 'history',
};

const NOTIFICATION_TOPIC = 'apsgo_notifications';
const NOTIFICATION_CHANNEL_ID = 'apsgo_watering_channel';

const config = {
  redis: {
    host: process.env.REDIS_HOST || 'localhost',
    port: parseInt(process.env.REDIS_PORT) || 6379,
    password: process.env.REDIS_PASSWORD || undefined,
    maxRetriesPerRequest: null,
  },

  firebase: {
    projectId: process.env.FIREBASE_PROJECT_ID,
    clientEmail: process.env.FIREBASE_CLIENT_EMAIL,
    privateKey: process.env.FIREBASE_PRIVATE_KEY?.replace(/\\n/g, '\n'),
    databaseURL: process.env.FIREBASE_DATABASE_URL,
  },

  worker: {
    concurrency: 1,
    checkInterval: 60000,
    sensorDebounce: 120000,
    scheduleGraceMs: parseInt(process.env.SCHEDULE_GRACE_MS || '15000', 10),
    scheduleMaxCatchupMs: parseInt(
      process.env.SCHEDULE_MAX_CATCHUP_MS || '300000',
      10
    ),
    breakBetweenPotsMs: 30000,
  },
};

console.log('🚀 Starting ApsGo Railway Worker...');
console.log(`📡 Firebase Project: ${config.firebase.projectId}`);
console.log(`🔥 Firebase DB URL: ${config.firebase.databaseURL}`);
console.log(`📦 Redis: ${config.redis.host}:${config.redis.port}`);
console.log(
  `⏰ Timezone: ${process.env.TZ} | Current: ${new Date().toLocaleString(
    'id-ID',
    { timeZone: 'Asia/Jakarta' }
  )}`
);
console.log(`📍 Kontrol Path: /${FIREBASE_PATHS.kontrol}`);

// ==================== ENVIRONMENT VALIDATION ====================

const requiredEnvs = [
  'FIREBASE_PROJECT_ID',
  'FIREBASE_CLIENT_EMAIL',
  'FIREBASE_PRIVATE_KEY',
  'FIREBASE_DATABASE_URL',
];

const missingEnvs = requiredEnvs.filter((env) => !process.env[env]);

if (missingEnvs.length > 0) {
  console.error('❌ Missing required environment variables:');

  missingEnvs.forEach((env) => {
    console.error(`   - ${env}`);
  });

  console.error('\n📋 To fix this:');
  console.error('1. Go to Railway Dashboard');
  console.error('2. Select your worker service');
  console.error('3. Go to Variables tab');
  console.error('4. Add the missing variables');
  console.error('5. Redeploy');

  process.exit(1);
}

console.log('✅ All required environment variables are set');

// ==================== FIREBASE INITIALIZATION ====================

try {
  admin.initializeApp({
    credential: admin.credential.cert({
      projectId: config.firebase.projectId,
      clientEmail: config.firebase.clientEmail,
      privateKey: config.firebase.privateKey,
    }),
    databaseURL: config.firebase.databaseURL,
  });

  console.log('✅ Firebase Admin initialized');
} catch (error) {
  console.error('❌ Firebase initialization failed:', error.message);
  process.exit(1);
}

const db = admin.database();

// ==================== NOTIFICATION FUNCTION ====================

async function sendAutomationNotification({ title, body, type, data = {} }) {
  try {
    if (!admin.messaging) {
      console.log(
        '📝 [INFO] Cloud Messaging not available. App will use local notifications via Firebase listener.'
      );
      return;
    }

    const messaging = admin.messaging();

    if (!messaging) {
      console.log(
        '📝 [INFO] Cloud Messaging SDK not initialized. App will use local notifications via Firebase listener.'
      );
      return;
    }

    const result = await messaging.send({
      topic: NOTIFICATION_TOPIC,

      notification: {
        title,
        body,
      },

      data: {
        type: String(type || 'automation'),
        title: String(title || ''),
        body: String(body || ''),
        ...Object.fromEntries(
          Object.entries(data).map(([key, value]) => [
            key,
            String(value),
          ])
        ),
      },

      android: {
        priority: 'high',
        notification: {
          channelId: NOTIFICATION_CHANNEL_ID,
          icon: 'ic_launcher',
          color: '#2E7D32',
        },
      },
    });

    console.log(`🔔 FCM Notification sent: ${type} | ID: ${result}`);
  } catch (error) {
    console.log(
      `📝 [INFO] FCM not available (${error.message}). App will use local notifications via Firebase listener.`
    );
  }
}

// ==================== FIREBASE CONNECTION STATUS ====================

db.ref('.info/connected').on('value', (snap) => {
  if (snap.val() === true) {
    console.log('🔌 Firebase realtime connection active');
  } else {
    console.log('⚠️ Firebase realtime connection inactive');
  }
});

// ==================== REDIS & QUEUE SETUP ====================

const redis = new Redis(config.redis);

const wateringQueue = new Queue('watering', {
  connection: redis,
});

redis.on('connect', () => {
  console.log('✅ Redis connected');
});

redis.on('error', (err) => {
  console.error('❌ Redis error:', err.message);
});

// Track cooldown threshold
const lastThresholdTime = {};

// ==================== FIREBASE SMART HELPER ====================

let consecutiveFirebaseErrors = 0;
let sdkSuccessCount = 0;
let restFallbackCount = 0;

const SKIP_SDK_THRESHOLD = 3;
const RESET_THRESHOLD_AFTER = 50;

async function fetchWithTimeout(ref, timeoutMs = 5000) {
  return Promise.race([
    ref.once('value'),
    new Promise((_, reject) =>
      setTimeout(() => reject(new Error('Firebase fetch timeout')), timeoutMs)
    ),
  ]);
}

async function fetchWithTimeout2(url, options = {}, timeoutMs = 8000) {
  const controller = new AbortController();
  const timeout = setTimeout(() => controller.abort(), timeoutMs);

  try {
    const response = await fetch(url, {
      ...options,
      signal: controller.signal,
    });

    clearTimeout(timeout);
    return response;
  } catch (error) {
    clearTimeout(timeout);

    if (error.name === 'AbortError') {
      throw new Error('REST API timeout');
    }

    throw error;
  }
}

async function fetchKontrolViaREST() {
  const url = `${config.firebase.databaseURL}/${FIREBASE_PATHS.kontrol}.json`;

  console.log(`   [DEBUG] Trying REST API: ${url}`);

  const response = await fetchWithTimeout2(
    url,
    {
      method: 'GET',
      headers: {
        'Content-Type': 'application/json',
      },
    },
    8000
  );

  if (!response.ok) {
    throw new Error(`REST API failed: ${response.status} ${response.statusText}`);
  }

  const data = await response.json();

  console.log('   [DEBUG] REST API successful!');
  return data;
}

async function fetchKontrolSmart() {
  const shouldSkipSDK = consecutiveFirebaseErrors >= SKIP_SDK_THRESHOLD;

  if (shouldSkipSDK) {
    console.log(
      '   [SMART] Skipping SDK because 3+ consecutive failures. Using REST API directly...'
    );

    try {
      const data = await fetchKontrolViaREST();

      restFallbackCount++;

      if (restFallbackCount >= RESET_THRESHOLD_AFTER) {
        console.log('   [SMART] Resetting SDK retry counter...');
        consecutiveFirebaseErrors = 0;
        restFallbackCount = 0;
      }

      return data;
    } catch (restError) {
      console.error('   ❌ REST API failed:', restError.message);
      throw new Error('REST API failed');
    }
  }

  try {
    console.log('   [DEBUG] Attempting SDK fetch...');

    const snapshot = await fetchWithTimeout(
      db.ref(FIREBASE_PATHS.kontrol),
      5000
    );

    consecutiveFirebaseErrors = 0;
    restFallbackCount = 0;
    sdkSuccessCount++;

    return snapshot.val();
  } catch (sdkError) {
    console.warn('   ⚠️ SDK fetch failed, trying REST API...');

    consecutiveFirebaseErrors++;

    try {
      const data = await fetchKontrolViaREST();

      restFallbackCount++;

      if (consecutiveFirebaseErrors === SKIP_SDK_THRESHOLD) {
        console.warn(
          `   🚨 SDK failed ${SKIP_SDK_THRESHOLD}x consecutively. REST API will be used directly.`
        );
      }

      return data;
    } catch (restError) {
      console.error('   ❌ REST API also failed:', restError.message);
      throw new Error('Both SDK and REST API failed');
    }
  }
}

async function updateFirebaseViaREST(path, updates) {
  const url = `${config.firebase.databaseURL}/${path}.json`;

  console.log(`   [DEBUG] REST API PATCH: ${url}`);

  const response = await fetchWithTimeout2(
    url,
    {
      method: 'PATCH',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(updates),
    },
    8000
  );

  if (!response.ok) {
    throw new Error(`REST PATCH failed: ${response.status}`);
  }

  const result = await response.json();

  console.log('   [DEBUG] REST API update successful!');
  return result;
}

async function updateWithTimeout(ref, updates, timeoutMs = 5000) {
  return Promise.race([
    ref.update(updates),
    new Promise((_, reject) =>
      setTimeout(() => reject(new Error('Firebase update timeout')), timeoutMs)
    ),
  ]);
}

async function updateFirebaseSmart(path, updates, maxAttempts = 3) {
  const updateStr = JSON.stringify(updates);

  console.log(`   [UPDATE START] Path: /${path}, Data: ${updateStr}`);
  console.log(`   [UPDATE] Max attempts: ${maxAttempts}`);

  let lastError = null;

  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    try {
      const shouldSkipSDK = consecutiveFirebaseErrors >= SKIP_SDK_THRESHOLD;

      if (shouldSkipSDK) {
        console.log(
          `   [UPDATE Attempt ${attempt}/${maxAttempts}] Using REST API directly`
        );

        await updateFirebaseViaREST(path, updates);

        console.log(`   ✅ [UPDATE] REST API successful on attempt ${attempt}`);
        return true;
      }

      console.log(
        `   [UPDATE Attempt ${attempt}/${maxAttempts}] Attempting SDK update...`
      );

      await updateWithTimeout(db.ref(path), updates, 5000);

      console.log(`   ✅ [UPDATE] SDK update successful on attempt ${attempt}`);
      return true;
    } catch (sdkError) {
      lastError = sdkError;

      console.warn(
        `   ⚠️ [UPDATE Attempt ${attempt}/${maxAttempts}] SDK failed: ${sdkError.message}`
      );

      try {
        console.log(
          `   [UPDATE Attempt ${attempt}/${maxAttempts}] Fallback to REST API...`
        );

        await updateFirebaseViaREST(path, updates);

        console.log(`   ✅ [UPDATE] REST API successful on attempt ${attempt}`);
        return true;
      } catch (restError) {
        lastError = restError;

        console.error(
          `   ❌ [UPDATE Attempt ${attempt}/${maxAttempts}] REST failed: ${restError.message}`
        );

        if (attempt < maxAttempts) {
          const delayMs = attempt * 1000;

          console.log(`   ⏳ [UPDATE] Retrying after ${delayMs}ms...`);

          await new Promise((resolve) => setTimeout(resolve, delayMs));
        }
      }
    }
  }

  console.error(`   ❌ [UPDATE] ALL ${maxAttempts} ATTEMPTS FAILED!`);
  console.error(`   ❌ [UPDATE] Last error: ${lastError.message}`);

  throw new Error(
    `Firebase update failed after ${maxAttempts} attempts: ${lastError.message}`
  );
}

async function setFirebaseViaREST(path, data) {
  const url = `${config.firebase.databaseURL}/${path}.json`;

  const response = await fetchWithTimeout2(
    url,
    {
      method: 'PUT',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(data),
    },
    8000
  );

  if (!response.ok) {
    throw new Error(`REST PUT failed: ${response.status}`);
  }

  return await response.json();
}

async function setWithTimeout(ref, data, timeoutMs = 5000) {
  return Promise.race([
    ref.set(data),
    new Promise((_, reject) =>
      setTimeout(() => reject(new Error('Firebase set timeout')), timeoutMs)
    ),
  ]);
}

async function setFirebaseSmart(path, data) {
  const dataStr = JSON.stringify(data);

  console.log(`   [SET START] Path: ${path}, Data: ${dataStr.substring(0, 100)}...`);

  const shouldSkipSDK = consecutiveFirebaseErrors >= SKIP_SDK_THRESHOLD;

  if (shouldSkipSDK) {
    console.log('   [SET] Using REST API directly');

    try {
      await setFirebaseViaREST(path, data);

      console.log('   ✅ [SET] REST API successful!');
      return true;
    } catch (restError) {
      console.error(`   ❌ [SET] REST failed: ${restError.message}`);
      throw new Error('REST set failed');
    }
  }

  try {
    console.log('   [SET] Attempting SDK set...');

    await setWithTimeout(db.ref(path), data, 5000);

    console.log('   ✅ [SET] SDK set successful!');
    return true;
  } catch (sdkError) {
    console.warn(
      `   ⚠️ [SET] SDK failed (${sdkError.message}), trying REST API...`
    );

    try {
      await setFirebaseViaREST(path, data);

      console.log('   ✅ [SET] REST API successful!');
      return true;
    } catch (restError) {
      console.error('   ❌ [SET] REST failed. Both methods failed.');
      throw new Error('Both SDK and REST set failed');
    }
  }
}

async function readFirebaseSmart(path) {
  const shouldSkipSDK = consecutiveFirebaseErrors >= SKIP_SDK_THRESHOLD;

  if (shouldSkipSDK) {
    console.log('   [READ] Using REST API directly');

    const url = `${config.firebase.databaseURL}/${path}.json`;

    const response = await fetchWithTimeout2(
      url,
      {
        method: 'GET',
        headers: {
          'Content-Type': 'application/json',
        },
      },
      8000
    );

    if (!response.ok) {
      throw new Error(`REST GET failed: ${response.status}`);
    }

    return await response.json();
  }

  try {
    const snapshot = await fetchWithTimeout(db.ref(path), 5000);

    return snapshot.val();
  } catch (sdkError) {
    const url = `${config.firebase.databaseURL}/${path}.json`;

    const response = await fetchWithTimeout2(
      url,
      {
        method: 'GET',
        headers: {
          'Content-Type': 'application/json',
        },
      },
      8000
    );

    if (!response.ok) {
      throw new Error(`REST GET failed: ${response.status}`);
    }

    return await response.json();
  }
}

// ==================== DATE & HISTORY HELPERS ====================

function getTanggalIndonesia(now) {
  return `${now.getDate()}/${now.getMonth() + 1}/${now.getFullYear()}`;
}

function getWaktuIndonesia(now) {
  return `${now.getHours().toString().padStart(2, '0')}:${now
    .getMinutes()
    .toString()
    .padStart(2, '0')}`;
}

function getDateKey(now) {
  return `${now.getFullYear()}-${(now.getMonth() + 1)
    .toString()
    .padStart(2, '0')}-${now.getDate().toString().padStart(2, '0')}`;
}

function getModeFromType(type) {
  const t = String(type || '').toLowerCase();

  if (t.includes('waktu')) return 'waktu';
  if (t.includes('sensor')) return 'sensor';
  if (t.includes('threshold')) return 'sensor';
  if (t.includes('manual')) return 'manual';

  return 'otomatis';
}

// ==================== HISTORY LOGGING ====================

async function logHistory(type, potNumbers, duration) {
  try {
    const now = new Date();
    const sensorData = await readFirebaseSmart(FIREBASE_PATHS.data) || {};
    const mode = getModeFromType(type);

    console.log('   📝 Saving penyiraman history per pot...');

    for (const pot of potNumbers) {
      const historyData = {
        timestamp: now.getTime(),
        tanggal: getTanggalIndonesia(now),
        waktu: getWaktuIndonesia(now),

        jenis_histori: 'penyiraman',
        source: 'railway_worker',
        mode: mode,
        type: type,

        pot: pot,
        nama_pot: `Pot ${pot}`,

        suhu: Number(sensorData.suhu || 0),
        kelembapan: Number(sensorData.kelembapan || 0),
        soil: Number(sensorData[`soil_${pot}`] || 0),

        durasi: Number(duration || 0),
      };

      await db.ref(FIREBASE_PATHS.history).push().set(historyData);

      console.log(
        `   ✅ Histori penyiraman tersimpan: Pot ${pot}, Mode ${mode}, Soil ${historyData.soil}%`
      );
    }

    console.log(`   📊 History penyiraman logged: ${potNumbers.length} pot`);
  } catch (error) {
    console.error('   ⚠️ Failed to log penyiraman history:', error.message);
  }
}

async function logMonitoringHistory() {
  try {
    const sensorData = await readFirebaseSmart(FIREBASE_PATHS.data);

    if (!sensorData) {
      console.log('⚠️ Auto-log skipped: sensor data kosong');
      return;
    }

    const now = new Date();

    console.log('📊 Saving monitoring history every 30 minutes...');

    for (let pot = 1; pot <= 5; pot++) {
      const historyData = {
        timestamp: now.getTime(),
        tanggal: getTanggalIndonesia(now),
        waktu: getWaktuIndonesia(now),

        jenis_histori: 'monitoring',
        source: 'railway_worker',
        mode: '-',
        type: 'auto_log',

        pot: pot,
        nama_pot: `Pot ${pot}`,

        suhu: Number(sensorData.suhu || 0),
        kelembapan: Number(sensorData.kelembapan || 0),
        soil: Number(sensorData[`soil_${pot}`] || 0),

        durasi: 0,
      };

      await db.ref(FIREBASE_PATHS.history).push().set(historyData);

      console.log(
        `   ✅ Histori monitoring tersimpan: Pot ${pot}, Soil ${historyData.soil}%`
      );
    }

    console.log('📊 Auto-logged monitoring data selesai');
  } catch (error) {
    console.error('❌ Auto-log monitoring failed:', error.message);
  }
}

// ==================== SCHEDULE HELPER ====================

function parseScheduleTimeToday(scheduleTime, now) {
  if (typeof scheduleTime !== 'string' || !/^\d{2}:\d{2}$/.test(scheduleTime)) {
    return null;
  }

  const [hourStr, minuteStr] = scheduleTime.split(':');
  const hour = Number(hourStr);
  const minute = Number(minuteStr);

  if (
    Number.isNaN(hour) ||
    Number.isNaN(minute) ||
    hour < 0 ||
    hour > 23 ||
    minute < 0 ||
    minute > 59
  ) {
    return null;
  }

  const scheduledAt = new Date(now);
  scheduledAt.setHours(hour, minute, 0, 0);

  return scheduledAt;
}

function isScheduleInTriggerWindow(scheduleTime, now, windowStartMs, windowEndMs) {
  const scheduleDate = parseScheduleTimeToday(scheduleTime, now);

  if (!scheduleDate) {
    return {
      match: false,
      scheduleMs: null,
    };
  }

  const scheduleMs = scheduleDate.getTime();
  const match = scheduleMs >= windowStartMs && scheduleMs <= windowEndMs;

  return {
    match,
    scheduleMs,
  };
}

// ==================== WATERING WORKER ====================

const wateringWorker = new Worker(
  'watering',
  async (job) => {
    const {
      type,
      potNumbers,
      pompaAir,
      pompaPupuk,
      duration,
      scheduleId,
      thresholdId,
      smartMode,
      sensorData,
    } = job.data;

    console.log(`\n💧 Processing Job: ${job.id}`);
    console.log(`   Type: ${type}`);
    console.log(`   Pots: [${potNumbers.join(', ')}]`);
    console.log(`   Mode: ${smartMode ? 'SMART' : 'FIXED'}`);
    console.log(`   Duration: ${duration}s`);
    console.log(`   Break antar pot: ${config.worker.breakBetweenPotsMs / 1000} detik`);

    if (sensorData) {
      console.log(`   Target: ${sensorData.batasBawah}% → ${sensorData.batasAtas}%`);
    }

    try {
      const queueCounts = await wateringQueue.getJobCounts();

      console.log('\n   📊 QUEUE STATUS AT JOB START:');
      console.log(`      Active jobs: ${queueCounts.active}`);
      console.log(`      Waiting jobs: ${queueCounts.waiting}`);
    } catch (queueError) {
      console.warn(`   ⚠️ Could not get queue status: ${queueError.message}`);
    }

    try {
      const potCount = potNumbers.length;
      let totalWateringTime = 0;

      console.log('\n🛡️ SAFETY: Ensuring all pot valves are OFF at start...');

      await updateFirebaseSmart(
        FIREBASE_PATHS.aktuator,
        {
          mosvet_3: false,
          mosvet_4: false,
          mosvet_5: false,
          mosvet_6: false,
          mosvet_7: false,
        },
        2
      );

      console.log('✅ All pot valves confirmed OFF');
      await sleep(2000);

      if (String(type || '').startsWith('waktu_')) {
        const scheduleTime = job.data.scheduleTime || 'jadwal';
        const potText =
          potCount > 1 ? `pot ${potNumbers.join(', ')}` : `pot ${potNumbers[0]}`;

        await sendAutomationNotification({
          title: 'ApsGo - Jadwal Penyiraman',
          body: `Penyiraman bergiliran untuk ${potText} dimulai dari jam ${scheduleTime}.`,
          type: 'schedule_triggered',
          data: {
            scheduleId: scheduleId || '',
            scheduleTime,
            pots: potNumbers.join(','),
            duration,
            sequence: 'serial',
          },
        });
      }

      console.log(
        `\n🔄 STARTING SEQUENTIAL WATERING: ${potCount} pot akan disiram bergiliran`
      );

      for (let potIndex = 0; potIndex < potCount; potIndex++) {
        const pot = potNumbers[potIndex];
        const potSequence = potIndex + 1;

        console.log('\n   ╔════════════════════════════════════════╗');
        console.log(`   ║ POT ${pot} [${potSequence}/${potCount}]`.padEnd(40) + ' ║');
        console.log('   ╚════════════════════════════════════════╝');

        const offOthersUpdates = {};

        for (const otherPot of potNumbers) {
          if (otherPot !== pot && otherPot >= 1 && otherPot <= 5) {
            offOthersUpdates[`mosvet_${otherPot + 2}`] = false;
          }
        }

        if (Object.keys(offOthersUpdates).length > 0) {
          console.log(
            `   🔴 Turn OFF other pots: ${Object.keys(offOthersUpdates).join(', ')}`
          );

          await updateFirebaseSmart(FIREBASE_PATHS.aktuator, offOthersUpdates, 2);
          await sleep(1000);
        }

        const onUpdates = {};

        if (pompaAir) onUpdates.mosvet_1 = true;
        if (pompaPupuk) onUpdates.mosvet_2 = true;

        if (pot >= 1 && pot <= 5) {
          onUpdates[`mosvet_${pot + 2}`] = true;
        }

        console.log(`   🔛 Turn ON: ${Object.keys(onUpdates).join(', ')}`);

        await updateFirebaseSmart(FIREBASE_PATHS.aktuator, onUpdates, 3);

        let verified = false;

        for (let i = 0; i < 3; i++) {
          try {
            const currentState = await readFirebaseSmart(FIREBASE_PATHS.aktuator);

            const allSet = Object.keys(onUpdates).every(
              (key) => currentState[key] === onUpdates[key]
            );

            if (allSet) {
              console.log('   ✅ Verified: Valve ON successfully');
              verified = true;
              break;
            }

            console.warn(`   ⚠️ Verification attempt ${i + 1}/3 failed`);

            if (i < 2) {
              await sleep(500);
            }
          } catch (verifyError) {
            console.warn(
              `   ⚠️ Verification read failed attempt ${i + 1}/3: ${verifyError.message}`
            );
          }
        }

        if (!verified) {
          console.warn('   ⚠️ Valve verification not fully confirmed, continuing...');
        }

        if (smartMode && sensorData && sensorData.batasAtas) {
          const targetSoil = sensorData.batasAtas;
          const maxDurationMs = duration * 1000;
          const startTime = Date.now();

          let potCompleted = false;

          console.log(`   🎯 SMART MODE: Monitoring POT ${pot}, target ${targetSoil}%`);

          while (Date.now() - startTime < maxDurationMs && !potCompleted) {
            await sleep(2000);

            try {
              const currentSensorData = await readFirebaseSmart(FIREBASE_PATHS.data);
              const soilKey = `soil_${pot}`;
              const currentValue = parseInt(currentSensorData[soilKey]) || 0;
              const elapsed = Math.floor((Date.now() - startTime) / 1000);

              console.log(
                `   ⏳ [${elapsed}s] POT ${pot}: ${currentValue}% target ${targetSoil}%`
              );

              if (currentValue >= targetSoil) {
                console.log(
                  `   ✅ POT ${pot}: TARGET REACHED ${currentValue}% >= ${targetSoil}%`
                );

                potCompleted = true;
                totalWateringTime += elapsed;
              }
            } catch (sensorError) {
              console.warn(`   ⚠️ Failed to read sensor: ${sensorError.message}`);
            }
          }

          if (!potCompleted) {
            console.log(`   ⏱️ Max duration ${duration}s reached for POT ${pot}`);
            totalWateringTime += duration;
          }
        } else {
          console.log(`   ⏱️ FIXED MODE: Watering POT ${pot} for ${duration}s`);

          const startTime = Date.now();
          const endTime = startTime + duration * 1000;

          while (Date.now() < endTime) {
            const remaining = Math.ceil((endTime - Date.now()) / 1000);

            if (remaining % 10 === 0 || remaining <= 5) {
              console.log(`   ⏳ POT ${pot}: ${remaining}s remaining...`);
            }

            await sleep(1000);
          }

          totalWateringTime += duration;
        }

                const offUpdates = {};

        if (pot >= 1 && pot <= 5) {
          offUpdates[`mosvet_${pot + 2}`] = false;
        }

        if (pompaAir) offUpdates.mosvet_1 = false;
        if (pompaPupuk) offUpdates.mosvet_2 = false;

        console.log(
          `   🔴 Turn OFF POT + PUMPS: ${Object.keys(offUpdates).join(', ')}`
        );

        await updateFirebaseSmart(FIREBASE_PATHS.aktuator, offUpdates, 2);

        console.log('   ✅ Pot and pumps OFF confirmed');

        if (potIndex < potCount - 1) {
          console.log('   ⏳ Waiting 2 seconds to stabilize...');
          await sleep(2000);

          const nextPot = potNumbers[potIndex + 1];
          const breakSeconds = config.worker.breakBetweenPotsMs / 1000;

          console.log(`\n   ⏸️ BREAK ${breakSeconds} DETIK sebelum POT ${nextPot}...`);

          for (let breakTime = breakSeconds; breakTime > 0; breakTime--) {
            if (breakTime % 10 === 0 || breakTime <= 5) {
              console.log(`   ⏳ Break: ${breakTime}s remaining...`);
            }

            await sleep(1000);
          }
        }
      }

      console.log('\n   ✅ Semua pot selesai disiram!');

      const pumpStop = {};

      if (pompaAir) pumpStop.mosvet_1 = false;
      if (pompaPupuk) pumpStop.mosvet_2 = false;

      if (Object.keys(pumpStop).length > 0) {
        console.log(`   🔴 Turning OFF pumps: ${Object.keys(pumpStop).join(', ')}`);

        await updateFirebaseSmart(FIREBASE_PATHS.aktuator, pumpStop, 2);

        console.log('   ✅ Pumps stopped');
      }

      console.log(
        `   📝 Logging penyiraman history: ${potCount} pot, ${totalWateringTime}s total`
      );

      await logHistory(type, potNumbers, totalWateringTime);

      console.log('   ✅ History penyiraman logged successfully');

      if (thresholdId) {
        lastThresholdTime[thresholdId] = Date.now();

        console.log(`   ⏰ Cooldown set for ${thresholdId} | 2 minutes`);
      }

      console.log(`\n   ✅ Job completed successfully. Total: ${totalWateringTime}s`);

      return {
        success: true,
        duration: totalWateringTime,
        pots: potNumbers,
        mode: 'sequential',
      };
    } catch (error) {
      console.error('   ❌ Job failed:', error.message);
      console.error('   [ERROR DETAILS] Stack:', error.stack);

      const safetyUpdates = {
        mosvet_1: false,
        mosvet_2: false,
        mosvet_3: false,
        mosvet_4: false,
        mosvet_5: false,
        mosvet_6: false,
        mosvet_7: false,
        mosvet_8: false,
      };

      try {
        console.log('   🛡️ Safety: turning OFF all aktuators...');

        await updateFirebaseSmart(FIREBASE_PATHS.aktuator, safetyUpdates, 2);

        console.log('   🛡️ Safety: all aktuators turned OFF successfully');
      } catch (safetyError) {
        console.error('   ❌ CRITICAL: Safety OFF failed:', safetyError.message);
        console.error('   ❌ Penyiraman mungkin terjebak ON.');
      }

      throw error;
    }
  },
  {
    connection: redis,
    concurrency: config.worker.concurrency,
    lockDuration: 900000,
    removeOnComplete: {
      count: 100,
    },
    removeOnFail: {
      count: 50,
    },
  }
);

wateringWorker.on('completed', (job) => {
  console.log(`✅ Worker completed job ${job.id}`);
});

wateringWorker.on('failed', (job, err) => {
  console.error(`❌ Worker failed job ${job?.id}:`, err.message);
});

console.log('\n🔧 WATERING WORKER CONFIGURATION:');
console.log(`   Concurrency: ${config.worker.concurrency}`);
console.log('   Lock Duration: 900000ms');
console.log('   Remove on Complete: Keep 100 completed jobs');
console.log('   Remove on Fail: Keep 50 failed jobs');

if (config.worker.concurrency !== 1) {
  console.error('❌ CRITICAL: Worker concurrency is NOT 1!');
}

console.log('\n💧 SEQUENTIAL WATERING MODE ENABLED');
console.log('   - Pots watered one by one');
console.log(`   - Break between pots: ${config.worker.breakBetweenPotsMs / 1000}s`);
console.log('   - Applies to Waktu Mode and Sensor Mode');
console.log('   - Only 1 job runs at a time');

// ==================== WAKTU MODE ====================

let lastScheduleCheck = {};
let lastSchedulerTickAt = null;
let checkCounter = 0;

async function checkScheduledWatering() {
  checkCounter++;

  console.log(`\n🔎 checkScheduledWatering() called | Counter: ${checkCounter}`);

  try {
    const kontrolConfig = await fetchKontrolSmart();

    if (!kontrolConfig) {
      console.warn('   ⚠️ Kontrol config NULL. Schedule check aborted.');
      return;
    }

    const now = new Date();
    const checkEndMs = now.getTime();

    const currentTime = `${now.getHours().toString().padStart(2, '0')}:${now
      .getMinutes()
      .toString()
      .padStart(2, '0')}`;

    const currentSeconds = now.getSeconds();
    const dateKey = getDateKey(now);

    const graceMs = config.worker.scheduleGraceMs;
    const maxCatchupMs = config.worker.scheduleMaxCatchupMs;
    const fallbackStartMs = checkEndMs - config.worker.checkInterval;
    const baselineStartMs = lastSchedulerTickAt || fallbackStartMs;
    const boundedStartMs = Math.max(baselineStartMs, checkEndMs - maxCatchupMs);

    const triggerWindowStartMs = boundedStartMs - graceMs;
    const triggerWindowEndMs = checkEndMs;

    console.log(
      `\n⏱️ CHECK #${checkCounter}: ${currentTime}:${currentSeconds
        .toString()
        .padStart(2, '0')} | Mode Waktu: ${kontrolConfig?.waktu ? 'ON' : 'OFF'}`
    );

    const allSchedules = Object.keys(kontrolConfig).filter((key) =>
      key.startsWith('jadwal_')
    );

    if (allSchedules.length === 0) {
      console.warn('   ⚠️ No jadwal found in kontrol config.');
    }

    if (checkCounter % 3 === 0 || now.getMinutes() % 5 === 0) {
      console.log(`   📅 Date: ${dateKey}`);
      console.log(`   🕐 Current: ${currentTime}`);
      console.log(
        `   🪟 Trigger window: ${new Date(
          triggerWindowStartMs
        ).toLocaleTimeString('id-ID')} - ${new Date(
          triggerWindowEndMs
        ).toLocaleTimeString('id-ID')}`
      );
      console.log(`   📊 API Stats: SDK=${sdkSuccessCount}, REST=${restFallbackCount}`);
      console.log(`   📋 Total Jadwal: ${allSchedules.length}`);

      if (kontrolConfig?.waktu && allSchedules.length > 0) {
        allSchedules.forEach((scheduleKey) => {
          const schedule = kontrolConfig[scheduleKey];

          if (schedule && typeof schedule === 'object') {
            const isActive = schedule.aktif !== false;
            const waktu = schedule.waktu || 'not set';
            const potAktif = schedule.pot_aktif || [];

            console.log(
              `   ${isActive ? '✅' : '❌'} ${scheduleKey}: ${waktu} → Pot [${potAktif.join(
                ', '
              )}]`
            );
          }
        });
      }
    }

    if (!kontrolConfig.waktu) {
      console.log('   ⏭️ Mode waktu disabled. Skipping.');
      return;
    }

    for (const scheduleKey of allSchedules) {
      const schedule = kontrolConfig[scheduleKey];

      if (!schedule || typeof schedule !== 'object') {
        console.log(`   ⚠️ ${scheduleKey}: invalid structure. Skipping.`);
        continue;
      }

      const isActive = schedule.aktif !== false;

      if (!isActive) {
        continue;
      }

      const scheduleWaktu = schedule.waktu;

      const { match: isInWindow, scheduleMs } = isScheduleInTriggerWindow(
        scheduleWaktu,
        now,
        triggerWindowStartMs,
        triggerWindowEndMs
      );

      if (!isInWindow) {
        continue;
      }

      if (scheduleMs !== null) {
        const delayedSec = Math.max(
          0,
          Math.floor((triggerWindowEndMs - scheduleMs) / 1000)
        );

        if (delayedSec > 0) {
          console.log(
            `   ⏱️ ${scheduleKey}: Triggered with ${delayedSec}s delay`
          );
        }
      }

      const potAktif = schedule.pot_aktif || [];
      const durasi = schedule.durasi || 60;
      const pompaAir = schedule.pompa_air !== false;
      const pompaPupuk = schedule.pompa_pupuk || false;

      if (!Array.isArray(potAktif) || potAktif.length === 0) {
        console.log(`   ⚠️ ${scheduleKey}: no active pots. Skipping.`);
        continue;
      }

      const normalizedScheduleTime = scheduleWaktu.replace(':', '_');
      const jobKey = `${scheduleKey}_${dateKey}_${normalizedScheduleTime}`;

      if (lastScheduleCheck[jobKey]) {
        console.log(`   ⏭️ ${scheduleKey} already triggered: ${jobKey}`);
        continue;
      }

      console.log(`\n🕐 ${scheduleKey.toUpperCase()} TRIGGERED`);
      console.log(`   🎯 Pot aktif: [${potAktif.join(', ')}]`);
      console.log(`   ⏱️ Durasi: ${durasi}s`);
      console.log(`   💧 Pompa Air: ${pompaAir ? 'ON' : 'OFF'}`);
      console.log(`   🌿 Pompa Pupuk: ${pompaPupuk ? 'ON' : 'OFF'}`);

      try {
        await wateringQueue.add(
          scheduleKey,
          {
            type: `waktu_${scheduleKey}`,
            potNumbers: potAktif,
            pompaAir: pompaAir,
            pompaPupuk: pompaPupuk,
            duration: durasi,
            scheduleId: jobKey,
            scheduleTime: scheduleWaktu,
          },
          {
            jobId: jobKey,
            removeOnComplete: true,
          }
        );

        lastScheduleCheck[jobKey] = true;

        console.log(`   ✅ Successfully added to queue: ${jobKey}`);

        const queueStatus = await wateringQueue.getJobCounts();

        console.log(
          `   📊 Queue status: ${queueStatus.active} active, ${queueStatus.waiting} waiting`
        );
      } catch (queueError) {
        console.error(`   ❌ Failed to add ${scheduleKey}:`, queueError.message);
      }
    }

    // Legacy support waktu_1 dan waktu_2
    const legacySchedules = [
      {
        key: 'legacy_jadwal_1',
        waktu: kontrolConfig.waktu_1,
        durasi: kontrolConfig.durasi_1 || 60,
      },
      {
        key: 'legacy_jadwal_2',
        waktu: kontrolConfig.waktu_2,
        durasi: kontrolConfig.durasi_2 || 60,
      },
    ];

    for (const legacy of legacySchedules) {
      const legacyWindow = isScheduleInTriggerWindow(
        legacy.waktu,
        now,
        triggerWindowStartMs,
        triggerWindowEndMs
      );

      if (!legacyWindow.match) {
        continue;
      }

      const legacyTimeKey = legacy.waktu.replace(':', '_');
      const scheduleKey = `${legacy.key}_${dateKey}_${legacyTimeKey}`;

      if (lastScheduleCheck[scheduleKey]) {
        continue;
      }

      console.log(`\n🕐 [LEGACY] ${legacy.key.toUpperCase()} TRIGGERED`);

      try {
        await wateringQueue.add(
          legacy.key,
          {
            type: 'waktu_jadwal_legacy',
            potNumbers: [1, 2, 3, 4, 5],
            pompaAir: true,
            pompaPupuk: true,
            duration: legacy.durasi,
            scheduleId: scheduleKey,
            scheduleTime: legacy.waktu,
          },
          {
            jobId: scheduleKey,
            removeOnComplete: true,
          }
        );

        lastScheduleCheck[scheduleKey] = true;

        console.log(`   ✅ Successfully added legacy queue: ${scheduleKey}`);
      } catch (queueError) {
        console.error('   ❌ Failed legacy queue:', queueError.message);
      }
    }

    for (const key in lastScheduleCheck) {
      if (key.includes(dateKey)) {
        continue;
      }

      delete lastScheduleCheck[key];
    }
  } catch (error) {
    console.error('❌ Error checking scheduled watering:', error.message);
    console.error('[DEBUG] Stack:', error.stack);
  } finally {
    lastSchedulerTickAt = Date.now();
  }
}
// ==================== START WAKTU MODE SCHEDULER ====================

setInterval(async () => {
  try {
    await checkScheduledWatering();
  } catch (error) {
    console.error('❌ Error in scheduled check interval:', error.message);
  }
}, config.worker.checkInterval);

console.log(
  `✅ Waktu Mode scheduler started every ${config.worker.checkInterval / 1000}s`
);

setTimeout(async () => {
  try {
    console.log('\n🚀 Running first schedule check...');
    await checkScheduledWatering();
    console.log('✅ First schedule check completed');
  } catch (error) {
    console.error('❌ First schedule check failed:', error.message);
  }
}, 8000);

// ==================== SENSOR MODE ====================

let sensorCheckCounter = 0;

async function checkSensorThresholds() {
  sensorCheckCounter++;

  try {
    const sensorData = await readFirebaseSmart(FIREBASE_PATHS.data);

    if (!sensorData) {
      console.log('⚠️ Sensor data kosong. ESP32 mungkin belum mengirim data.');
      return;
    }

    const kontrolConfig = await fetchKontrolSmart();

    if (!kontrolConfig) {
      console.log('⚠️ Kontrol config kosong.');
      return;
    }

    if (!kontrolConfig.otomatis) {
      if (sensorCheckCounter % 10 === 0) {
        console.log('⚠️ Sensor mode disabled. Skipping threshold check.');
      }

      return;
    }

    const allThresholds = Object.keys(kontrolConfig).filter((key) =>
      key.startsWith('threshold_')
    );

    console.log(
      `\n🌡️ SENSOR CHECK #${sensorCheckCounter} | Total Thresholds: ${allThresholds.length}`
    );

    if (allThresholds.length === 0) {
      console.log('   ⚠️ No thresholds configured.');
      return;
    }

    for (const thresholdKey of allThresholds) {
      const threshold = kontrolConfig[thresholdKey];

      console.log(`\n   🔍 Checking ${thresholdKey}`);

      if (!threshold || !threshold.aktif) {
        console.log(
          `      ❌ Skipped: ${!threshold ? 'not found' : 'aktif=false'}`
        );
        continue;
      }

      const lastTime = lastThresholdTime[thresholdKey];

      if (lastTime && Date.now() - lastTime < config.worker.sensorDebounce) {
        const remainingSeconds = Math.ceil(
          (config.worker.sensorDebounce - (Date.now() - lastTime)) / 1000
        );

        console.log(
          `      ⏳ ${thresholdKey}: cooldown aktif ${remainingSeconds}s`
        );

        continue;
      }

      const batasBawah = threshold.batas_bawah || 30;
      const batasAtas = threshold.batas_atas || 70;
      const durasi = threshold.durasi || 600;
      const smartMode = threshold.smart_mode === true;
      const potAktif = threshold.pot_aktif || [];
      const pompaAir = threshold.pompa_air === true;
      const pompaPupuk = threshold.pompa_pupuk === true;

      const potsNeedWatering = [];
      const potDetails = [];

      for (const potNumber of potAktif) {
        if (potNumber < 1 || potNumber > 5) {
          console.log(`      ⚠️ POT ${potNumber}: invalid`);
          continue;
        }

        const soilKey = `soil_${potNumber}`;
        const soilValue = parseInt(sensorData[soilKey]) || 0;

        console.log(
          `      🌱 POT ${potNumber}: ${soilValue}% | Threshold ${batasBawah}-${batasAtas}%`
        );

        if (soilValue >= batasAtas) {
          console.log(
            `      ✅ POT ${potNumber}: skip, sudah basah ${soilValue}%`
          );
          continue;
        }

        if (soilValue < batasBawah) {
          console.log(
            `      🚨 POT ${potNumber} kering: ${soilValue}% < ${batasBawah}%`
          );

          potsNeedWatering.push(potNumber);
          potDetails.push({
            pot: potNumber,
            value: soilValue,
          });
        } else {
          console.log(`      ✅ POT ${potNumber}: OK`);
        }
      }

      if (potsNeedWatering.length > 0) {
        console.log(`\n🌡️ THRESHOLD TRIGGERED: ${thresholdKey}`);
        console.log(`   Pots needing water: [${potsNeedWatering.join(', ')}]`);
        console.log(`   Mode: ${smartMode ? 'Smart' : 'Fixed'}`);
        console.log(`   Durasi: ${durasi}s`);
        console.log(`   Pompa Air: ${pompaAir}`);
        console.log(`   Pompa Pupuk: ${pompaPupuk}`);

        await sendAutomationNotification({
          title: 'ApsGo - Penyiraman Otomatis',
          body: `Penyiraman dilakukan pada pot ${potsNeedWatering.join(
            ', '
          )} karena kelembapan di bawah ${batasBawah}%.`,
          type: 'sensor_triggered',
          data: {
            thresholdId: thresholdKey,
            pots: potsNeedWatering.join(','),
            batasBawah,
            batasAtas,
            durasi,
            mode: smartMode ? 'smart' : 'fixed',
          },
        });

        const jobId = `${thresholdKey}-${Date.now()}`;

        await wateringQueue.add(
          thresholdKey,
          {
            type: 'sensor_threshold',
            potNumbers: potsNeedWatering,
            pompaAir: pompaAir,
            pompaPupuk: pompaPupuk,
            duration: durasi,
            scheduleId: jobId,
            thresholdId: thresholdKey,
            smartMode: smartMode,
            sensorData: {
              batasBawah,
              batasAtas,
              mode: smartMode ? 'smart' : 'fixed',
              potValues: potDetails,
            },
          },
          {
            jobId,
            removeOnComplete: true,
            priority: 1,
          }
        );

        console.log(`   📌 Added to queue: ${jobId}`);
      }
    }
  } catch (error) {
    console.error('❌ Error in sensor threshold check:', error.message);
  }
}

async function setupSensorMonitoring() {
  console.log('\n🌡️ SENSOR MODE ENABLED');
  console.log('✅ Polling every 30 seconds');
  console.log(`📍 Config path: /${FIREBASE_PATHS.kontrol}`);

  setInterval(async () => {
    try {
      await checkSensorThresholds();
    } catch (error) {
      console.error('❌ Polling sensor check failed:', error.message);
    }
  }, 30000);

  setTimeout(async () => {
    try {
      console.log('🚀 Running first sensor check...');
      await checkSensorThresholds();
      console.log('✅ First sensor check completed');
    } catch (error) {
      console.error('❌ First sensor check failed:', error.message);
    }
  }, 10000);

  try {
    db.ref(FIREBASE_PATHS.data).on(
      'value',
      async () => {
        console.log('🔔 Firebase data listener triggered');
        await checkSensorThresholds();
      },
      (error) => {
        console.error('❌ Firebase listener error:', error.message);
      }
    );

    console.log('✅ Firebase data listener attached');
  } catch (error) {
    console.log('⚠️ Listener failed, polling tetap berjalan.');
  }
}

setupSensorMonitoring();

// ==================== PERIODIC MONITORING HISTORY ====================

const autoLogJob = new cron.CronJob('*/30 * * * *', async () => {
  await logMonitoringHistory();
});

autoLogJob.start();

console.log('✅ Auto monitoring history started every 30 minutes');

// ==================== CLEANUP OLD HISTORY ====================

const cleanupJob = new cron.CronJob('0 2 * * *', async () => {
  try {
    console.log('\n🧹 Running history cleanup...');

    const daysToKeep = 10;
    const cutoffDate = new Date();

    cutoffDate.setDate(cutoffDate.getDate() - daysToKeep);

    const historyData = await readFirebaseSmart(FIREBASE_PATHS.history);

    if (!historyData) {
      console.log('🧹 History empty. Cleanup skipped.');
      return;
    }

    let deletedCount = 0;

    for (const historyId in historyData) {
      const item = historyData[historyId];

      if (!item || !item.timestamp) {
        continue;
      }

      const itemDate = new Date(item.timestamp);

      if (itemDate < cutoffDate) {
        const url = `${config.firebase.databaseURL}/${FIREBASE_PATHS.history}/${historyId}.json`;

        await fetchWithTimeout2(
          url,
          {
            method: 'DELETE',
          },
          10000
        );

        deletedCount++;

        console.log(`   🗑️ Deleted history item: ${historyId}`);
      }
    }

    console.log(`✅ Cleanup completed: ${deletedCount} item removed`);
  } catch (error) {
    console.error('❌ Cleanup failed:', error.message);
  }
});

cleanupJob.start();

console.log('✅ History cleanup scheduled daily at 2 AM');

// ==================== UTILITIES ====================

function sleep(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

// ==================== DIAGNOSTIC FUNCTIONS ====================

async function checkAktuatorNode() {
  try {
    console.log('\n🔍 Checking aktuator node...');

    const aktuatorData = await readFirebaseSmart(FIREBASE_PATHS.aktuator);

    if (!aktuatorData) {
      console.log('❌ Aktuator node not found. Creating default...');

      await setFirebaseSmart(FIREBASE_PATHS.aktuator, {
        mosvet_1: false,
        mosvet_2: false,
        mosvet_3: false,
        mosvet_4: false,
        mosvet_5: false,
        mosvet_6: false,
        mosvet_7: false,
        mosvet_8: false,
      });

      console.log('✅ Aktuator node created');
      return;
    }

    const required = [
      'mosvet_1',
      'mosvet_2',
      'mosvet_3',
      'mosvet_4',
      'mosvet_5',
      'mosvet_6',
      'mosvet_7',
      'mosvet_8',
    ];

    const missing = required.filter((key) => !(key in aktuatorData));

    if (missing.length > 0) {
      const updates = {};

      missing.forEach((key) => {
        updates[key] = false;
      });

      await updateFirebaseSmart(FIREBASE_PATHS.aktuator, updates);

      console.log(`✅ Missing mosvets added: ${missing.join(', ')}`);
    } else {
      console.log('✅ Aktuator node complete');
    }
  } catch (error) {
    console.error('❌ Aktuator check failed:', error.message);
  }
}

async function showCurrentTime() {
  try {
    const now = new Date();

    console.log('\n🕐 CURRENT TIME ANALYSIS');
    console.log(`   Server Local: ${now.toString()}`);
    console.log(
      `   Asia/Jakarta: ${now.toLocaleString('id-ID', {
        timeZone: 'Asia/Jakarta',
      })}`
    );
    console.log(`   ISO: ${now.toISOString()}`);
    console.log(`   Unix: ${now.getTime()}`);
    console.log(`   TZ Env: ${process.env.TZ}`);
    console.log(`   HH:MM: ${getWaktuIndonesia(now)}`);
  } catch (error) {
    console.error('❌ Time check failed:', error.message);
  }
}

async function healthCheck() {
  try {
    const firebaseOk = !!config.firebase.databaseURL;

    await redis.ping();

    const queueStatus = await wateringQueue.getJobCounts();

    console.log('\n💚 HEALTH CHECK');
    console.log(`   Firebase: ${firebaseOk ? '✅' : '❌'}`);
    console.log('   Redis: ✅');
    console.log(
      `   Queue: ${queueStatus.active} active, ${queueStatus.waiting} waiting`
    );
  } catch (error) {
    console.error('❤️‍🩹 HEALTH CHECK FAILED:', error.message);
  }
}

// ==================== HEALTH CHECK ====================

setInterval(healthCheck, 300000);

setTimeout(healthCheck, 5000);

// ==================== GRACEFUL SHUTDOWN ====================

async function shutdown() {
  console.log('\n🛑 Shutting down gracefully...');

  try {
    await wateringWorker.close();
    console.log('✅ Worker closed');

    await wateringQueue.close();
    console.log('✅ Queue closed');

    await redis.quit();
    console.log('✅ Redis disconnected');

    await admin.app().delete();
    console.log('✅ Firebase disconnected');

    process.exit(0);
  } catch (error) {
    console.error('❌ Shutdown error:', error.message);
    process.exit(1);
  }
}

process.on('SIGTERM', shutdown);
process.on('SIGINT', shutdown);

// ==================== PREVENT CRASHES ====================

process.on('uncaughtException', (error) => {
  console.error('❌ Uncaught Exception:', error.message);
  console.error(error.stack);
  console.log('⚠️ Worker continuing despite error...');
});

process.on('unhandledRejection', (reason, promise) => {
  console.error('❌ Unhandled Rejection at:', promise);
  console.error('Reason:', reason);
  console.log('⚠️ Worker continuing despite rejection...');
});

// ==================== STARTUP COMPLETE ====================

console.log('\n✨ ApsGo Railway Worker is running!');
console.log('📊 Features enabled:');
console.log('   • Waktu Mode');
console.log('   • Sensor Mode');
console.log('   • History Penyiraman dengan jenis_histori');
console.log('   • History Monitoring setiap 30 menit');
console.log('   • Cleanup History');
console.log('   • Health Check');
console.log('\n🎯 Worker is ready to process jobs...\n');

// ==================== STARTUP DIAGNOSTICS ====================

setTimeout(async () => {
  try {
    console.log('🔍 Verifying Firebase connection...');

    const snapshot = await fetchWithTimeout(
      db.ref(FIREBASE_PATHS.kontrol),
      10000
    );

    const data = snapshot.val();

    if (data) {
      console.log(`✅ Firebase /${FIREBASE_PATHS.kontrol} readable`);
      console.log(`   Mode waktu: ${data.waktu ? 'ENABLED' : 'DISABLED'}`);
      console.log(`   Mode sensor: ${data.otomatis ? 'ENABLED' : 'DISABLED'}`);
    } else {
      console.log(`⚠️ Firebase /${FIREBASE_PATHS.kontrol} empty`);
    }
  } catch (error) {
    console.error('❌ Firebase verification failed:', error.message);
  }
}, 3000);

setTimeout(async () => {
  try {
    console.log('\n🔧 RUNNING DIAGNOSTIC CHECKS...');
    await showCurrentTime();
    await checkAktuatorNode();
    console.log('✅ Diagnostic checks completed');
  } catch (error) {
    console.error('❌ Diagnostic checks failed:', error.message);
  }
}, 5000);

// ==================== KEEP ALIVE ====================

setInterval(() => {
  const uptime = Math.floor(process.uptime());
  const hours = Math.floor(uptime / 3600);
  const minutes = Math.floor((uptime % 3600) / 60);

  console.log(`💓 Heartbeat: Worker alive for ${hours}h ${minutes}m`);
}, 30000);

setInterval(() => {
  const now = new Date();

  if (now.getMinutes() % 10 === 0 && now.getSeconds() < 30) {
    showCurrentTime();
  }
}, 30000);