import net from 'net';
import mysql from 'mysql2/promise';
import axios from 'axios';
import http from 'http';
import https from 'https';

// Config
const DB_CONFIG = {
  host: '184.168.120.17',
  user: 'sjps_fibre_2025',
  password: 'OApZq%Zyom_^',
  database: 'sjps_fibre_2026',
  port: 3306,
  waitForConnections: true,
  connectionLimit: 100,
  queueLimit: 0,
  idleTimeout: 60_000,
  enableKeepAlive: true,
  keepAliveInitialDelay: 10_000,
}

// To be Changed on each Port
const TCP_PORT = 27106;
const TCP_UNIT = 1406;
let Reader = 'RFID Reader_1406';
let MSG_Status = 'WA_SERVICE';

const ALERT_MOBILE = '919829221171,918529081395';
const RECONNECT_DELAY = 1000; // ms

const nowStr = () => {
  const d = new Date();
  return `${('0' + d.getDate()).slice(-2)}/${('0' + (d.getMonth() + 1)).slice(-2)}/${d.getFullYear()} ${('0' + d.getHours()).slice(-2)}:${('0' + d.getMinutes()).slice(-2)}`;
};

// WA Providers
const TWINSCODER = {
  sendUrl: 'https://whatsapp-api.twinscoder.com/api/admin/whatsapp/send-message',
  instanceId: 'INTSYS',
  apiToken: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjZhMWFmMzA5ZTU0Y2E4MjVkZTk0YmU3NCIsInJvbGUiOjEsImp0aSI6IjcyZDNkZjJhLTZjMmQtNDIyOS05ZTNlLTAzNGExOTJlZjcyNiIsImlhdCI6MTc4MDE1MTA1NiwiZXhwIjoyMDk1NTExMDU2fQ.oeFbg1DyKkD-mMoVYgQXUUdlPV3NUBXNlllgTMkMr6s'
};

function formatDateTime(date = new Date()) {
  const datePart = date.toLocaleDateString('en-US', {
    month: 'long',
    day: 'numeric',
    year: 'numeric'
  });

  const timePart = date.toLocaleTimeString('en-US', {
    hour: 'numeric',
    minute: '2-digit',
    hour12: true
  });

  return `${datePart} at ${timePart}`;
}

async function checkWAStatus() {
  try {
    const response = await fetch(
      `https://whatsapp-api.twinscoder.com/api/admin/whatsapp/live-status?instanceId=${TWINSCODER.instanceId}`,
      {
        headers: {
          'X-API-Token': TWINSCODER.apiToken
        }
      }
    );

    const wanow = new Date();
    const wahour = wanow.getHours();
    const data = await response.json();
    const connected = data?.data?.isConnected === true;
    console.log(connected ? 'TWINSCODER Connected' : 'TWINSCODER Not Connected');
    if (!connected) {
      if (wahour >= 6 && wahour < 20) {
        const msgsms = `Dear Parents,\nTWINSCODER left school on ${formatDateTime()}\n\n*Shri Jain Public School*`;
        const smstidsms = '1707175162147951736';
        await sendSMS_CIsms(ALERT_MOBILE, msgsms, smstidsms);
        MSG_Status = 'SMS_SERVICE';
      }
    } else {
      if (wahour === 6) {
        const msgwa = `TWINSCODER Active on ${formatDateTime()} at SJPS`;
        await sendWA(ALERT_MOBILE, msgwa);
      }
      MSG_Status = 'WA_SERVICE';
    }
    return connected;
  } catch (error) {
    console.log('Not Connected');
    console.error('Check WA Status Error:', error.message);
    return false;
  }
}

// Initial check + schedule every hour
checkWAStatus();
//setInterval(checkWAStatus, 3600000);

// Globals
const pool = mysql.createPool(DB_CONFIG);
const IGNORE_PATTERNS = ['build:', '�', '@��'];
let lastProcessedEPC = null;
let serverInstance = null;
let isShuttingDown = false;
let lastNotifiedIP = null; // Track IP across server restarts

// Axios Keep-Alive
const httpAgent = new http.Agent({ keepAlive: true, maxSockets: 64, keepAliveMsecs: 30_000 });
const httpsAgent = new https.Agent({ keepAlive: true, maxSockets: 64, keepAliveMsecs: 30_000 });

const httpClient = axios.create({
  timeout: 20_000,
  httpAgent,
  httpsAgent,
  headers: { Connection: 'keep-alive' },
});

// DB Helpers
async function markMessageResult(id, remarks, mType) {
  const conn = await pool.getConnection();
  const mStatus = mType + 2000;
  try {
    await conn.execute(
      `UPDATE s_epc_live
       SET remarks = ?, m_status = ?, m_datetime = NOW()
       WHERE id = ?`,
      [String(remarks ?? ''), mStatus, id]
    );
  } finally {
    conn.release();
  }
}

// Senders - Simplified (only WA)
async function sendWA(mobile91, msg) {
  try {
    const mobiles = mobile91.split(",").map(m => m.trim());
    const results = [];
    for (const mobile of mobiles) {
      const response = await httpClient.post(
        `${TWINSCODER.sendUrl}?instanceId=${TWINSCODER.instanceId}`,
        {
          to: mobile,
          message: msg
        },
        {
          headers: {
            'X-API-Token': TWINSCODER.apiToken,
            'Content-Type': 'application/json'
          }
        }
      );
      results.push(response.data);
    }
    return results;
  } catch (err) {
    console.error('WA send failed:', err.message);
    return { error: err.message };
  }
}

async function sendSMS_CIsms(mobile91, msg, templateId) {
  const url = `https://messaging.charteredinfo.com/smsaspx?ID=${encodeURIComponent(
    'info.surana@gmail.com'
  )}&Pwd=${encodeURIComponent('Ast@28831123')}&PhNo=${encodeURIComponent(
    mobile91
  )}&Text=${encodeURIComponent(msg)}&TemplateID=${encodeURIComponent(templateId)}`;

  const { data } = await httpClient.get(url);
  return data;
}

async function notifyAdmin(msg, smstid) {
  try {
    if (MSG_Status == 'WA_SERVICE')
      await sendWA(ALERT_MOBILE, msg);
    else
      await sendSMS_CIsms(ALERT_MOBILE, msg, smstid);

    console.log(`${Reader} Admin alert sent.`);
  } catch (e) {
    console.error(`${Reader} Admin notify failed:`, e.message);
  }
}

// Core DB Flow
async function insertAndMaybeSend(epc) {
  let conn;
  try {
    conn = await pool.getConnection();

    const [result] = await conn.execute(
      'INSERT INTO `s_epc_live` (`epc`, `inout_datetime`, `unit_id`) VALUES (?, ?, ?)',
      [epc, new Date(), TCP_UNIT]
    );

    const insertedId = result.insertId;
    const [rows] = await conn.execute(
      `SELECT
         id, 
         s_pid as pid,
         s_name AS name,
         mobile,
         in_out,
         m_type,
         DATE_FORMAT(inout_datetime, '%M %d, %Y at %l:%i %p') AS DT
       FROM s_epc_live
       WHERE id = ?
         AND m_status = 0
         AND (in_out = 'IN' OR in_out = 'OUT')`,
      [insertedId]
    );

    if (!rows?.length) {
      console.log(`${Reader} No message for EPC: ${epc}`);
      return;
    }

    const { id, pid, name, mobile, in_out: inOut, DT, m_type: mType } = rows[0];
    const formattedMobile = `91${mobile}`;

    let msg = '';
    let smstid = '';

    if (inOut === 'OUT' && pid <= 9000) {
      msg = `Dear Parents,\n${name} arrived at school on ${DT}\n\n*Shri Jain Public School*`;
      smstid = '1707175153856501744';
    } else if (inOut === 'IN') {
      msg = `Dear Parents,\n${name} arrived at school on ${DT}\n\n*Shri Jain Public School*`;
      smstid = '1707175153856501744';
    } else if (inOut === 'OUT') {
      msg = `Dear Parents,\n${name} left school on ${DT}\n\n*Shri Jain Public School*`;
      smstid = '1707175162147951736';
    }

    if (mType === 2) {
      const resp = await sendWA(formattedMobile, msg);
      await markMessageResult(id, JSON.stringify(resp), mType);
    } else if (mType === 4) {
      const resp = await sendSMS_CIsms(formattedMobile, msg, smstid);
      await markMessageResult(id, JSON.stringify(resp), mType);
    } else {
      await markMessageResult(id, `No route for m_type=${mType}`, mType);
    }
    console.log(`${Reader} Message Sent for Inserted EPC: ${epc} on ${nowStr()}`);
  } catch (error) {
    console.error('DB flow error:', error.message);
  } finally {
    if (conn) conn.release();
  }
}

// Function to log port connection status
async function logPortConnection(currentIP, isConnected) {
  // Only log if we have a valid IP
  if (!currentIP || currentIP === '0.0.0.0') {
    return;
  }

  let conn;
  try {
    conn = await pool.getConnection();
    const message = isConnected ? 'server connected' : 'server disconnected';
    await conn.execute(
      'INSERT INTO `port_connection` (`unit_id`, `current_ip`, `message`, `datetime`) VALUES (?, ?, ?, NOW())',
      [TCP_UNIT, currentIP, message]
    );
    console.log(`${Reader} Port connection logged: ${message} for IP ${currentIP}`);
  } catch (error) {
    console.error('Error logging port connection:', error.message);
  } finally {
    if (conn) conn.release();
  }
}

// TCP Server
function createServer() {
  const server = net.createServer((socket) => {
    let buffer = '';
    const currentIP = socket.remoteAddress.replace(/^::ffff:/, '')

    // Log connection when client connects
    logPortConnection(currentIP, true);

    if (lastNotifiedIP !== currentIP) {
      lastNotifiedIP = currentIP;
      const ipName = `${Reader}_${currentIP.replace(/\./g, '_')}`;
      const smstid = '1707175153856501744';
      const msg = `Dear Parents,\n${ipName} arrived at school on ${formatDateTime()}\n\n*Shri Jain Public School*`;
      notifyAdmin(msg, smstid).catch(() => { });
      console.log(`${Reader} New IP connection, admin notified`);
    } else {
      console.log(`${Reader} Same IP reconnected, admin NOT notified`);
    }

    socket.on('data', (data) => {
      try {
        buffer += data.toString();

        let startIdx = buffer.indexOf('@');
        while (startIdx !== -1) {
          const endIdx = buffer.indexOf('$', startIdx);
          if (endIdx === -1) break;

          const fullTag = buffer.substring(startIdx, endIdx + 1);
          const epc = fullTag.slice(1, -1);

          const shouldIgnore = IGNORE_PATTERNS.some(
            (pattern) =>
              fullTag.includes(pattern) || epc.includes(pattern)
          );

          if (!shouldIgnore && epc.length > 0 && epc !== lastProcessedEPC) {
            lastProcessedEPC = epc;
            insertAndMaybeSend(epc).catch(console.error);
          }

          buffer = buffer.substring(endIdx + 1);
          startIdx = buffer.indexOf('@');
        }

        if (buffer.length > 2048)
          buffer = '';
      } catch (error) {
        console.error('Error processing data:', error.message);
      }
    });

    socket.on('error', (err) => {
      console.error(`${Reader} Socket Error:`, err.message);
    });

    socket.on('end', () => {
      console.log(`${Reader} Disconnected:`, currentIP);
      // Log disconnection when client disconnects
      logPortConnection(currentIP, false);
    });
  });

  server.on('error', (err) => {
    console.error(`${Reader} Server error:`, err.message);
    if (!isShuttingDown)
      setTimeout(startServer, RECONNECT_DELAY);
  });

  return server;
}

function startServer() {
  if (serverInstance) {
    try {
      serverInstance.close();
    } catch (err) {
      console.error(`${Reader} Error closing server:`, err.message);
    }
  }

  serverInstance = createServer();
  serverInstance.listen(TCP_PORT, () => {
    console.log(`${Reader} listening on port ${TCP_PORT} on ${nowStr()}`);
  });
}

// Function to check and log server status every 15 minutes
async function logServerStatus() {
  let conn;
  try {
    // Check if server is running and has a valid IP
    const isRunning = serverInstance && serverInstance.listening;
    const currentIP = lastNotifiedIP;

    // ONLY insert if we have a valid IP (not 0.0.0.0) and server is running
    if (currentIP && currentIP !== '0.0.0.0' && isRunning) {
      conn = await pool.getConnection();
      await conn.execute(
        'INSERT INTO `port_connection` (`unit_id`, `current_ip`, `message`, `datetime`) VALUES (?, ?, ?, NOW())',
        [TCP_UNIT, currentIP, 'server connected']
      );
    }
  } catch (error) {
    console.error('Error logging server status:', error.message);
  } finally {
    if (conn) conn.release();
  }
}

// Schedule status logging every 15 minutes (900000 ms)
setInterval(logServerStatus, 900000);

// Also log initial status when server starts
setTimeout(() => {
  logServerStatus();
}, 5000); // Wait 5 seconds after server start

// Graceful Shutdown
async function shutdown() {
  if (isShuttingDown) return;
  isShuttingDown = true;

  console.log(`${Reader} Shutting down \non ${nowStr()}`);
  try {
    if (serverInstance) serverInstance.close();
    await pool.end();
    console.log(`${Reader} closed gracefully \non ${nowStr()}`);
    process.exit(0);
  } catch (err) {
    console.error(`${Reader} Shutdown error:`, err.message);
    process.exit(1);
  }
}

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

// Start the server initially
startServer();