import express, { Request, Response } from "express";
import path from "path";
import { GoogleGenAI } from "@google/genai";
import dotenv from "dotenv";
import fs from "fs";
import AdmZip from "adm-zip";
import crypto from "crypto";

const { registerMikrotikRoutes } = require("./lib/register-mikrotik-routes.cjs");
const { startRadiusServer } = require("./lib/radius-server.cjs");

// Load Environment Variables
dotenv.config();

const app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

// Register real RouterOS-backed routes before legacy compatibility handlers.
registerMikrotikRoutes(app, { cwd: process.cwd() });

const PORT = process.env.PORT || 3000;

// Setup Gemini SDK if API key is defined
let aiClient: GoogleGenAI | null = null;
try {
  const apiKey = process.env.GEMINI_API_KEY;
  if (apiKey && apiKey !== "MY_GEMINI_API_KEY") {
    aiClient = new GoogleGenAI({
      apiKey,
      httpOptions: {
        headers: {
          'User-Agent': 'aistudio-build',
        }
      }
    });
  }
} catch (error) {
  console.log("Gemini SDK Initialization failed or key missing. Continuing without AI features...");
}

// ==========================================
// APPLICATION STATE
// ==========================================

let tenants: any[] = [];

let subscriptionPlans = [
  { id: 1, name: "Starter", max_customers: 100, max_routers: 3, max_olts: 1, monthly_price: 150000 },
  { id: 2, name: "Professional", max_customers: 1000, max_routers: 20, max_olts: 5, monthly_price: 500000 },
  { id: 3, name: "Enterprise", max_customers: 999999, max_routers: 999, max_olts: 99, monthly_price: 1500000 }
];

let users = [
  {
    id: 9999,
    tenant_id: 0, // Global Platform Owner
    role_id: 1, // Super Admin / Web Owner
    name: "Ahmad Arsal (Pemilik Web)",
    email: "admin@mikrobil.com",
    phone: "081111111111",
    status: "active" as const,
    password: "admin",
    last_login: new Date().toISOString(),
    created_at: new Date().toISOString(),
    updated_at: new Date().toISOString()
  }
];

let packages: any[] = [];

let customers: any[] = [];

let customerNotes: any[] = [];

let customerDocuments: any[] = [];

let invoices: any[] = [];

let payments: any[] = [];

let pppoeAccounts: any[] = [];

let pppoeSessions: any[] = [];

let hotspotUsers: any[] = [];

let hotspotSessions: any[] = [];

let pppoeProfiles: any[] = [];

let hotspotProfiles: any[] = [];

let hotspotVoucherSales: any[] = [];

let ipBindings: any[] = [];

let hotspotHosts: any[] = [];

let gatewaySettings = {
  whatsapp_api_url: "https://api.fonnte.com",
  whatsapp_token: "",
  whatsapp_status: "disconnected",
  telegram_bot_token: "",
  telegram_chat_id: "",
  telegram_status: "disconnected"
};

let billingSettings = {
  auto_billing_enabled: true,
  auto_isolate_overdue: true,
  due_days: 10,
  template_invoice_created: "Yth. {customer_name}, Invoice {invoice_number} sebesar {invoice_amount} telah terbit. Silakan lakukan pembayaran sebelum tanggal {due_date} agar layanan internet {service_plan} tetap aktif. Terima kasih!",
  template_invoice_reminder: "Yth. {customer_name}, Mengingatkan kembali bahwa tagihan {invoice_number} sebesar {invoice_amount} akan jatuh tempo pada {due_date}. Silakan abaikan pesan ini jika sudah melakukan pembayaran.",
  template_invoice_overdue: "PERINGATAN! Yth. {customer_name}, tagihan {invoice_number} sebesar {invoice_amount} TELAH MELEWATI JATUH TEMPO ({due_date}). Layanan Anda akan diisolasi/dinonaktifkan sementara sampai dilakukan pembayaran.",
  template_invoice_paid: "Terima kasih! Pembayaran untuk Invoice {invoice_number} sebesar {invoice_amount} telah kami terima. Layanan internet Anda telah aktif kembali."
};

let radiusNas: any[] = [];

let radiusSessions: any[] = [];

let staticIps: any[] = [];

let resellers: any[] = [];

let resellerTransactions: any[] = [];

let routers: any[] = [];

let routerInterfaces: any[] = [];

let alerts: any[] = [];

let tickets: any[] = [];

let ticketComments: any[] = [];

let workOrders: any[] = [];

let acsDevices: any[] = [];

let acsParameters: any[] = [];

let olts: any[] = [];

let onus: any[] = [];

let networkNodes: any[] = [];

let networkLinks: any[] = [];

let whatsappLogs: any[] = [];

let telegramLogs: any[] = [];

let activityLogs: {
  id: number;
  tenant_id: number;
  user_id: number;
  user_name: string;
  action: string;
  entity_type: string;
  entity_id: any;
  ip_address: string;
  created_at: string;
  details?: string;
}[] = [];

const runtimeDataPath = path.join(process.cwd(), "runtime_data.json");

function applyRuntimeData() {
  if (!fs.existsSync(runtimeDataPath)) return;
  try {
    const saved = JSON.parse(fs.readFileSync(runtimeDataPath, "utf8"));
    if (Array.isArray(saved.tenants)) tenants = saved.tenants;
    if (Array.isArray(saved.users)) users = saved.users;
    if (Array.isArray(saved.subscriptionPlans)) subscriptionPlans = saved.subscriptionPlans;
    if (Array.isArray(saved.packages)) packages = saved.packages;
    if (Array.isArray(saved.customers)) customers = saved.customers;
    if (Array.isArray(saved.customerNotes)) customerNotes = saved.customerNotes;
    if (Array.isArray(saved.customerDocuments)) customerDocuments = saved.customerDocuments;
    if (Array.isArray(saved.invoices)) invoices = saved.invoices;
    if (Array.isArray(saved.payments)) payments = saved.payments;
    if (Array.isArray(saved.staticIps)) staticIps = saved.staticIps;
    if (Array.isArray(saved.resellers)) resellers = saved.resellers;
    if (Array.isArray(saved.resellerTransactions)) resellerTransactions = saved.resellerTransactions;
    if (Array.isArray(saved.tickets)) tickets = saved.tickets;
    if (Array.isArray(saved.ticketComments)) ticketComments = saved.ticketComments;
    if (Array.isArray(saved.workOrders)) workOrders = saved.workOrders;
    if (Array.isArray(saved.olts)) olts = saved.olts;
    if (Array.isArray(saved.onus)) onus = saved.onus;
    if (Array.isArray(saved.networkNodes)) networkNodes = saved.networkNodes;
    if (Array.isArray(saved.networkLinks)) networkLinks = saved.networkLinks;
    if (Array.isArray(saved.whatsappLogs)) whatsappLogs = saved.whatsappLogs;
    if (Array.isArray(saved.telegramLogs)) telegramLogs = saved.telegramLogs;
    if (Array.isArray(saved.activityLogs)) activityLogs = saved.activityLogs;
    if (saved.gatewaySettings) gatewaySettings = saved.gatewaySettings;
    if (saved.billingSettings) billingSettings = saved.billingSettings;
  } catch (error) {
    console.error("Gagal membaca runtime_data.json:", error);
  }
}

function saveRuntimeData() {
  const temporaryPath = runtimeDataPath + ".tmp";
  const data = {
    tenants, users, subscriptionPlans, packages, customers, customerNotes, customerDocuments,
    invoices, payments, staticIps, resellers, resellerTransactions, tickets, ticketComments,
    workOrders, olts, onus, networkNodes, networkLinks, whatsappLogs, telegramLogs,
    activityLogs, gatewaySettings, billingSettings
  };
  fs.writeFileSync(temporaryPath, JSON.stringify(data, null, 2), "utf8");
  fs.renameSync(temporaryPath, runtimeDataPath);
}

applyRuntimeData();
app.use("/api/v1", (req, res, next) => {
  if (!["POST", "PUT", "PATCH", "DELETE"].includes(req.method)) return next();
  res.on("finish", () => {
    if (res.statusCode < 400) {
      try { saveRuntimeData(); } catch (error) { console.error("Gagal menyimpan runtime_data.json:", error); }
    }
  });
  next();
});

function logAudit(action: string, entityType: string, entityId: number, details: string, userName = "Administrator", ipAddress = "127.0.0.1") {
  activityLogs.unshift({
    id: activityLogs.length + 1,
    tenant_id: 1,
    user_id: 1,
    user_name: userName,
    action: action,
    entity_type: entityType,
    entity_id: entityId,
    ip_address: ipAddress,
    created_at: new Date().toISOString(),
    details: details
  });
}

// Success / Error wrappers helper
const successRes = (data: any, message = "OK") => ({ status: "success", message, data });
const errorRes = (message: string, errors = {}) => ({ status: "error", message, errors });
const createSessionToken = () => crypto.randomBytes(32).toString("base64url");

// ==========================================
// 17 CORE API SPEC IMPLEMENTATIONS
// ==========================================

// --- 2. AUTH API ---
app.post("/api/v1/auth/register", (req, res) => {
  const { company_name, name, email, password, phone } = req.body;
  if (!company_name || !name || !email || !password) {
    return res.status(400).json(errorRes("Missing required registration fields."));
  }

  // Create new tenant
  const tenantId = tenants.length + 1;
  const newTenant = {
    id: tenantId,
    name: company_name,
    company_name,
    email,
    phone: phone || "0812345678",
    address: "PT " + company_name,
    domain: company_name.toLowerCase().replace(/\s+/g, "") + ".mikrobil.com",
    logo: "https://images.unsplash.com/photo-1516321318423-f06f85e504b3?w=100&h=100&fit=crop",
    status: "active" as const,
    subscription_plan_id: 1,
    created_at: new Date().toISOString(),
    updated_at: new Date().toISOString(),
  };
  tenants.push(newTenant);

  // Create User
  const userId = users.length + 1;
  const newUser = {
    id: userId,
    tenant_id: tenantId,
    role_id: 2, // Owner Role
    name,
    email,
    phone: phone || "",
    status: "active" as const,
    password,
    last_login: new Date().toISOString(),
    created_at: new Date().toISOString(),
    updated_at: new Date().toISOString()
  };
  users.push(newUser);

  // Generate some templates
  return res.json(successRes({
    token: createSessionToken(),
    user: newUser,
    tenant: newTenant
  }, "Tenant registered successfully"));
});

app.post("/api/v1/auth/login", (req, res) => {
  const { email, password } = req.body;
  
  if (!email || !password) {
    return res.status(400).json(errorRes("Email and password are required."));
  }

  const user = users.find(u => String(u.email).toLowerCase() === String(email).trim().toLowerCase());
  if (!user) {
    return res.status(401).json(errorRes("Email atau kata sandi salah."));
  }
  if (String(user.password || "") !== String(password)) return res.status(401).json(errorRes("Email atau kata sandi salah."));

  let tenant = tenants.find(t => t.id === user.tenant_id);
  if (!tenant && user.tenant_id === 0) {
    tenant = {
      id: 0,
      name: "MikRobiL SaaS",
      company_name: "MikRobiL Platform Management",
      email: "admin@mikrobil.com",
      phone: "081111111111",
      address: "MikRobiL HQ Office",
      domain: "mikrobil.com",
      logo: "https://images.unsplash.com/photo-1516321318423-f06f85e504b3?w=100&h=100&fit=crop",
      status: "active" as any,
      subscription_plan_id: 3,
      created_at: new Date().toISOString(),
      updated_at: new Date().toISOString()
    };
  }
  user.last_login = new Date().toISOString();

  return res.json(successRes({
    token: createSessionToken(),
    user,
    tenant
  }, "Logged in successfully"));
});

app.post("/api/v1/auth/logout", (req, res) => {
  res.json(successRes({}, "Logged out successfully"));
});

app.post("/api/v1/auth/refresh", (req, res) => {
  res.json(successRes({ token: createSessionToken() }, "Token refreshed"));
});

// --- 3. CUSTOMER API ---
app.get("/api/v1/customers", (req, res) => {
  res.json(successRes(customers));
});

app.post("/api/v1/customers", (req, res) => {
  const data = req.body;
  const newId = customers.length + 1;
  const custCode = `CUST-${String(newId).padStart(3, '0')}`;
  
  const newCustomer = {
    id: newId,
    tenant_id: 1,
    customer_code: custCode,
    full_name: data.full_name || "New Customer",
    email: data.email || "new@customer.com",
    phone: data.phone || "",
    address: data.address || "",
    latitude: Number(data.latitude) || -6.2,
    longitude: Number(data.longitude) || 106.8,
    package_id: Number(data.package_id) || 1,
    status: (data.status || "active") as any,
    activation_date: data.status === "active" ? new Date().toISOString().split("T")[0] : undefined,
    created_at: new Date().toISOString(),
    updated_at: new Date().toISOString(),
  };

  customers.push(newCustomer);

  // Auto create a PPPoE account if status is active
  if (data.status === "active") {
    pppoeAccounts.push({
      id: pppoeAccounts.length + 1,
      tenant_id: 1,
      customer_id: newId,
      username: (data.full_name || "cust").toLowerCase().replace(/\s+/g, "") + "@jayanet",
      service: "pppoe",
      profile: "Home-Lite",
      remote_address: `10.10.10.${10 + newId}`,
      status: "active" as const,
      created_at: new Date().toISOString(),
      updated_at: new Date().toISOString(),
    });
  }

  // Log activity
  activityLogs.unshift({
    id: activityLogs.length + 1,
    tenant_id: 1,
    user_id: 1,
    user_name: "Admin Jaya Net",
    action: "customer.create",
    entity_type: "Customer",
    entity_id: newId,
    ip_address: "127.0.0.1",
    created_at: new Date().toISOString()
  });

  res.json(successRes(newCustomer, "Customer created successfully"));
});

app.get("/api/v1/customers/:id", (req, res) => {
  const id = Number(req.params.id);
  const customer = customers.find(c => c.id === id);
  if (!customer) {
    return res.status(404).json(errorRes("Customer not found"));
  }

  const pppoe = pppoeAccounts.find(p => p.customer_id === id);
  const custInvoices = invoices.filter(i => i.customer_id === id);
  const custTickets = tickets.filter(t => t.customer_id === id);
  const custAcs = acsDevices.find(a => a.customer_id === id);
  const custNotes = customerNotes.filter(n => n.customer_id === id);
  const custDocs = customerDocuments.filter(d => d.customer_id === id);

  res.json(successRes({
    customer,
    pppoe,
    invoices: custInvoices,
    tickets: custTickets,
    acsDevice: custAcs,
    notes: custNotes,
    documents: custDocs
  }));
});

app.put("/api/v1/customers/:id", (req, res) => {
  const id = Number(req.params.id);
  const index = customers.findIndex(c => c.id === id);
  if (index === -1) {
    return res.status(404).json(errorRes("Customer not found"));
  }

  customers[index] = {
    ...customers[index],
    ...req.body,
    updated_at: new Date().toISOString()
  };

  logAudit("customer.update", "Customer", id, `Memperbarui data profil pelanggan CRM "${customers[index].full_name}" (ID: ${id})`);
  res.json(successRes(customers[index], "Customer updated successfully"));
});

app.delete("/api/v1/customers/:id", (req, res) => {
  const id = Number(req.params.id);
  const target = customers.find(c => c.id === id);
  const targetName = target ? target.full_name : `ID #${id}`;
  customers = customers.filter(c => c.id !== id);
  logAudit("customer.delete", "Customer", id, `Menghapus data pelanggan CRM "${targetName}" secara permanen dari server.`);
  res.json(successRes({}, "Customer deleted successfully"));
});

app.post("/api/v1/customers/:id/suspend", (req, res) => {
  const id = Number(req.params.id);
  const index = customers.findIndex(c => c.id === id);
  if (index === -1) return res.status(404).json(errorRes("Customer not found"));

  customers[index].status = "suspended";
  
  // Also disable related PPPoE
  const pppIndex = pppoeAccounts.findIndex(p => p.customer_id === id);
  if (pppIndex !== -1) {
    pppoeAccounts[pppIndex].status = "disabled";
  }

  // Disconnect active PPPoE sessions
  const pppAcc = pppoeAccounts.find(p => p.customer_id === id);
  if (pppAcc) {
    pppoeSessions = pppoeSessions.filter(s => s.pppoe_account_id !== pppAcc.id);
  }

  // Create alert and logs
  alerts.unshift({
    id: alerts.length + 1,
    tenant_id: 1,
    severity: "warning",
    title: "Customer Suspended",
    message: `${customers[index].full_name} has been suspended dynamically. Internet disabled.`,
    timestamp: new Date().toISOString(),
    is_active: true
  });

  activityLogs.unshift({
    id: activityLogs.length + 1,
    tenant_id: 1,
    user_id: 1,
    user_name: "Admin Jaya Net",
    action: "customer.suspend",
    entity_type: "Customer",
    entity_id: id,
    ip_address: "127.0.0.1",
    created_at: new Date().toISOString()
  });

  res.json(successRes(customers[index], "Customer suspended successfully"));
});

app.post("/api/v1/customers/:id/activate", (req, res) => {
  const id = Number(req.params.id);
  const index = customers.findIndex(c => c.id === id);
  if (index === -1) return res.status(404).json(errorRes("Customer not found"));

  customers[index].status = "active";
  
  // Reactivate PPPoE
  const pppIndex = pppoeAccounts.findIndex(p => p.customer_id === id);
  if (pppIndex !== -1) {
    pppoeAccounts[pppIndex].status = "active";
  }

  // Log
  activityLogs.unshift({
    id: activityLogs.length + 1,
    tenant_id: 1,
    user_id: 1,
    user_name: "Admin Jaya Net",
    action: "customer.unsuspend",
    entity_type: "Customer",
    entity_id: id,
    ip_address: "127.0.0.1",
    created_at: new Date().toISOString()
  });

  res.json(successRes(customers[index], "Customer activated successfully"));
});

// --- 4. BILLING API ---
app.post("/api/v1/billing/invoices/generate", (req, res) => {
  const generated: any[] = [];
  customers.forEach(cust => {
    if (cust.status === "active") {
      const packageObj = packages.find(p => p.id === cust.package_id);
      const activeInvoices = invoices.filter(i => i.customer_id === cust.id && i.status !== "paid");
      
      // Only generate if no active unpaid invoices to prevent double billing
      if (activeInvoices.length === 0) {
        const invId = invoices.length + generated.length + 1;
        const invNum = `INV/202606/${String(invId).padStart(4, '0')}`;
        const amount = packageObj ? packageObj.price : 150000;
        
        const newInv = {
          id: invId,
          tenant_id: 1,
          customer_id: cust.id,
          invoice_number: invNum,
          amount,
          due_date: new Date(Date.now() + 10 * 24 * 60 * 60 * 1000).toISOString(),
          status: "unpaid" as const,
          generated_at: new Date().toISOString(),
          created_at: new Date().toISOString(),
          updated_at: new Date().toISOString()
        };
        generated.push(newInv);
      }
    }
  });

  invoices.push(...generated);

  activityLogs.unshift({
    id: activityLogs.length + 1,
    tenant_id: 1,
    user_id: 1,
    user_name: "Admin Jaya Net",
    action: "invoice.generate",
    entity_type: "Invoice",
    entity_id: generated.length,
    ip_address: "127.0.0.1",
    created_at: new Date().toISOString()
  });

  res.json(successRes(generated, `Billing run completed. Generated ${generated.length} invoices.`));
});

app.get("/api/v1/billing/invoices", (req, res) => {
  res.json(successRes(invoices));
});

// GET automated billing settings & templates
app.get("/api/v1/billing/settings", (req, res) => {
  res.json(successRes(billingSettings));
});

// UPDATE automated billing settings & templates
app.put("/api/v1/billing/settings", (req, res) => {
  billingSettings = { ...billingSettings, ...req.body };
  res.json(successRes(billingSettings, "Konfigurasi Automasi & Templat Tagihan berhasil disimpan!"));
});

// Trigger a manual run of the Automated Billing Engine
app.post("/api/v1/billing/run-check", (req, res) => {
  const result = runAutomatedBillingEngine(true);
  res.json(successRes(result, "Engine Automasi Tagihan dikoordinasikan & berhasil dijalankan!"));
});

app.get("/api/v1/billing/invoices/:id", (req, res) => {
  const id = Number(req.params.id);
  const invoice = invoices.find(inv => inv.id === id);
  if (!invoice) return res.status(404).json(errorRes("Invoice not found"));

  const customer = customers.find(c => c.id === invoice.customer_id);
  const items = [
    { id: 1, invoice_id: id, description: `Paket Internet Bulanan (${customer?.full_name})`, qty: 1, unit_price: invoice.amount, total: invoice.amount }
  ];

  res.json(successRes({ invoice, customer, items }));
});

app.post("/api/v1/billing/invoices/:id/remind", (req, res) => {
  const id = Number(req.params.id);
  const invoice = invoices.find(inv => inv.id === id);
  if (!invoice) return res.status(404).json(errorRes("Invoice not found"));

  const customer = customers.find(c => c.id === invoice.customer_id);
  if (!customer) return res.status(404).json(errorRes("Customer not found"));

  // Queue the reminder; delivery requires a configured gateway.
  const msgId = whatsappLogs.length + 1;
  const packageObj = packages.find(p => p.id === customer.package_id);
  
  let textMsg = billingSettings.template_invoice_reminder
    .replace(/{customer_name}/g, customer.full_name)
    .replace(/{invoice_number}/g, invoice.invoice_number)
    .replace(/{invoice_amount}/g, "Rp " + invoice.amount.toLocaleString("id-ID"))
    .replace(/{due_date}/g, new Date(invoice.due_date).toLocaleDateString("id-ID"))
    .replace(/{service_plan}/g, packageObj ? packageObj.name : "Internet Plan");
  
  whatsappLogs.unshift({
    id: msgId,
    tenant_id: 1,
    customer_id: customer.id,
    phone: customer.phone,
    message: textMsg,
    status: "pending" as const,
    sent_at: new Date().toISOString()
  });

  res.json(successRes({}, "Pengingat masuk antrean dan belum ditandai terkirim."));
});

app.post("/api/v1/payments/create", (req, res) => {
  const { invoice_id, gateway } = req.body;
  const invoice = invoices.find(i => i.id === Number(invoice_id));
  if (!invoice) return res.status(404).json(errorRes("Invoice not found"));

  const ref = `PAY-${(gateway || "MIDTRANS").toUpperCase()}-${Date.now()}`;
  const payment = {
    id: payments.length + 1,
    tenant_id: 1,
    invoice_id: invoice.id,
    payment_gateway: gateway || "Midtrans",
    payment_reference: ref,
    amount: invoice.amount,
    status: "pending" as const,
    paid_at: ""
  };
  payments.push(payment);

  res.json(successRes(payment, "Payment request generated successfully"));
});

app.post("/api/v1/payments/webhook", (req, res) => {
  const { payment_reference, status } = req.body;
  const payIndex = payments.findIndex(p => p.payment_reference === payment_reference);
  if (payIndex === -1) return res.status(404).json(errorRes("Payment reference not found"));

  payments[payIndex].status = status || "success";
  payments[payIndex].paid_at = new Date().toISOString();

  if (payments[payIndex].status === "success") {
    // Update invoice to Paid
    const invIndex = invoices.findIndex(i => i.id === payments[payIndex].invoice_id);
    if (invIndex !== -1) {
      invoices[invIndex].status = "paid";
      invoices[invIndex].updated_at = new Date().toISOString();

      // Automatically reactivate suspended customers!
      const custId = invoices[invIndex].customer_id;
      const custIndex = customers.findIndex(c => c.id === custId);
      if (custIndex !== -1 && customers[custIndex].status === "suspended") {
        customers[custIndex].status = "active";
        
        // Reactivate related PPPoE
        const pppIndex = pppoeAccounts.findIndex(p => p.customer_id === custId);
        if (pppIndex !== -1) {
          pppoeAccounts[pppIndex].status = "active";
        }
      }

      // Live log message using template
      const templateCustObj = customers.find(c => c.id === custId);
      const templatePackObj = templateCustObj ? packages.find(p => p.id === templateCustObj.package_id) : null;
      let templateResultMsg = billingSettings.template_invoice_paid
        .replace(/{customer_name}/g, templateCustObj ? templateCustObj.full_name : "Pelanggan")
        .replace(/{invoice_number}/g, invoices[invIndex].invoice_number)
        .replace(/{invoice_amount}/g, "Rp " + invoices[invIndex].amount.toLocaleString("id-ID"))
        .replace(/{due_date}/g, new Date(invoices[invIndex].due_date).toLocaleDateString("id-ID"))
        .replace(/{service_plan}/g, templatePackObj ? templatePackObj.name : "Internet Plan");

      whatsappLogs.unshift({
        id: whatsappLogs.length + 1,
        tenant_id: 1,
        customer_id: custId,
        phone: templateCustObj?.phone || "0812345678",
        message: templateResultMsg,
        status: "sent" as const,
        sent_at: new Date().toISOString()
      });
    }
  }

  res.json(successRes(payments[payIndex], "Callback processed successfully"));
});

// --- STATIC IP SERVICES API ---
app.get("/api/v1/static-ips", (req, res) => {
  res.json(successRes(staticIps));
});

app.post("/api/v1/static-ips", (req, res) => {
  const { customer_id, name, interface: iface, static_ip, gateway, mac_address, rate_limit, status } = req.body;
  if (!name || !static_ip) return res.status(450).json(errorRes("Nama dan Static IP wajib diisi"));
  
  const newStatic = {
    id: staticIps.length ? Math.max(...staticIps.map(s => s.id)) + 1 : 1,
    customer_id: customer_id ? Number(customer_id) : null,
    name,
    interface: iface || "ether3-Lan",
    static_ip,
    gateway: gateway || "192.168.10.1",
    mac_address: mac_address || "any",
    rate_limit: rate_limit || "20M/20M",
    status: status || "active"
  };
  staticIps.push(newStatic);
  res.json(successRes(newStatic, "Layanan IP Statik berhasil ditambahkan"));
});

app.put("/api/v1/static-ips/:id", (req, res) => {
  const id = Number(req.params.id);
  const { customer_id, name, interface: iface, static_ip, gateway, mac_address, rate_limit, status } = req.body;
  const idx = staticIps.findIndex(s => s.id === id);
  if (idx === -1) return res.status(404).json(errorRes("Layanan IP Statik tidak ditemukan"));
  
  staticIps[idx] = {
    ...staticIps[idx],
    customer_id: customer_id !== undefined ? (customer_id ? Number(customer_id) : null) : staticIps[idx].customer_id,
    name: name || staticIps[idx].name,
    interface: iface || staticIps[idx].interface,
    static_ip: static_ip || staticIps[idx].static_ip,
    gateway: gateway || staticIps[idx].gateway,
    mac_address: mac_address !== undefined ? mac_address : staticIps[idx].mac_address,
    rate_limit: rate_limit || staticIps[idx].rate_limit,
    status: status || staticIps[idx].status
  };
  res.json(successRes(staticIps[idx], "Layanan IP Statik berhasil diperbarui"));
});

app.delete("/api/v1/static-ips/:id", (req, res) => {
  const id = Number(req.params.id);
  staticIps = staticIps.filter(s => s.id !== id);
  res.json(successRes({ id }, "Layanan IP Statik berhasil dihapus"));
});

// --- RESELLERS / AGENTS API ---
app.get("/api/v1/resellers", (req, res) => {
  res.json(successRes(resellers));
});

app.post("/api/v1/resellers", (req, res) => {
  const { name, code, phone, commission_pct, balance, status } = req.body;
  if (!name || !code) return res.status(400).json(errorRes("Nama dan Kode Agen wajib diisi"));
  
  const tokenCode = code.toUpperCase();
  if (resellers.some(r => r.code === tokenCode)) {
    return res.status(400).json(errorRes("Kode Agen sudah digunakan"));
  }

  const newReseller = {
    id: resellers.length ? Math.max(...resellers.map(r => r.id)) + 1 : 1,
    name,
    code: tokenCode,
    phone: phone || "",
    balance: Number(balance) || 0,
    commission_pct: Number(commission_pct) || 10,
    total_sales: 0,
    status: status || "active"
  };
  resellers.push(newReseller);
  res.json(successRes(newReseller, "Reseller / Agen berhasil didaftarkan"));
});

app.put("/api/v1/resellers/:id", (req, res) => {
  const id = Number(req.params.id);
  const { name, phone, commission_pct, balance, status } = req.body;
  const idx = resellers.findIndex(r => r.id === id);
  if (idx === -1) return res.status(404).json(errorRes("Reseller tidak ditemukan"));
  
  resellers[idx] = {
    ...resellers[idx],
    name: name || resellers[idx].name,
    phone: phone || resellers[idx].phone,
    commission_pct: commission_pct !== undefined ? Number(commission_pct) : resellers[idx].commission_pct,
    balance: balance !== undefined ? Number(balance) : resellers[idx].balance,
    status: status || resellers[idx].status
  };
  res.json(successRes(resellers[idx], "Reseller / Agen berhasil diperbarui"));
});

app.delete("/api/v1/resellers/:id", (req, res) => {
  const id = Number(req.params.id);
  resellers = resellers.filter(r => r.id !== id);
  res.json(successRes({ id }, "Reseller / Agen berhasil dihapus"));
});

app.get("/api/v1/resellers/transactions", (req, res) => {
  res.json(successRes(resellerTransactions));
});

app.post("/api/v1/resellers/:id/topup", (req, res) => {
  const id = Number(req.params.id);
  const { amount, description } = req.body;
  const topupAmt = Number(amount);
  if (isNaN(topupAmt) || topupAmt <= 0) return res.status(400).json(errorRes("Jumlah topup salah"));
  
  const idx = resellers.findIndex(r => r.id === id);
  if (idx === -1) return res.status(404).json(errorRes("Reseller tidak ditemukan"));
  
  resellers[idx].balance += topupAmt;
  
  const tx = {
    id: resellerTransactions.length + 1,
    reseller_id: id,
    type: "topup",
    amount: topupAmt,
    commission: 0,
    description: description || "Topup Manual Saldo Agen oleh Admin",
    created_at: new Date().toISOString()
  };
  resellerTransactions.push(tx);
  res.json(successRes(resellers[idx], `Topup Rp ${topupAmt.toLocaleString("id-ID")} berhasil`));
});

// --- COMMS GATEWAY SETTINGS API ---
app.get("/api/v1/comms/gateway-settings", (req, res) => {
  res.json(successRes(gatewaySettings));
});

app.put("/api/v1/comms/gateway-settings", (req, res) => {
  gatewaySettings = { ...gatewaySettings, ...req.body };
  res.json(successRes(gatewaySettings, "Konfigurasi Gateway berhasil disimpan!"));
});

// --- 9. ACS API (GENIEACS) ---
app.get("/api/v1/acs/devices", (req, res) => {
  res.json(successRes(acsDevices));
});

app.get("/api/v1/acs/devices/:id", (req, res) => {
  const device = acsDevices.find(a => a.id === Number(req.params.id));
  if (!device) return res.status(404).json(errorRes("ACS device not found"));
  const parameters = acsParameters.filter(p => p.device_id === device.id);
  res.json(successRes({ device, parameters }));
});

app.post("/api/v1/acs/devices/:id/reboot", (req, res) => {
  res.status(501).json(errorRes("GenieACS belum dikonfigurasi; reboot tidak dikirim."));
});

app.post("/api/v1/acs/devices/:id/provision", (req, res) => {
  res.status(501).json(errorRes("GenieACS belum dikonfigurasi; provisioning tidak dikirim."));
});

app.post("/api/v1/acs/devices/:id/upgrade", (req, res) => {
  res.status(501).json(errorRes("GenieACS belum dikonfigurasi; upgrade tidak dikirim."));
});

// --- 10. OLT API ---
app.get("/api/v1/olt", (req, res) => {
  res.json(successRes(olts));
});

app.get("/api/v1/olt/:id/onus", (req, res) => {
  res.json(successRes(onus.filter(o => o.olt_id === Number(req.params.id))));
});

app.post("/api/v1/olt/:id/onus/register", (req, res) => {
  if (!req.body.serial_number) return res.status(400).json(errorRes("Serial number ONU wajib diisi."));
  const newOnuId = onus.length + 1;
  const newOnu = {
    id: newOnuId,
    tenant_id: 1,
    olt_id: Number(req.params.id),
    customer_id: req.body.customer_id ? Number(req.body.customer_id) : null,
    serial_number: req.body.serial_number,
    status: "pending" as const,
    rx_power: null,
    tx_power: null,
    created_at: new Date().toISOString(),
    updated_at: new Date().toISOString()
  };
  onus.push(newOnu);
  res.json(successRes(newOnu, "ONU dicatat sebagai pending; aktivasi menunggu integrasi OLT."));
});

// --- 11. NETWORK MAP API ---
app.get("/api/v1/network/nodes", (req, res) => {
  res.json(successRes(networkNodes));
});

app.post("/api/v1/network/nodes", (req, res) => {
  const { node_type, name, latitude, longitude } = req.body;
  const nextId = networkNodes.length > 0 ? Math.max(...networkNodes.map(n => n.id)) + 1 : 1;
  if (!name || !Number.isFinite(Number(latitude)) || !Number.isFinite(Number(longitude))) return res.status(400).json(errorRes("Nama, latitude, dan longitude wajib diisi."));
  const newNode = {
    id: nextId,
    tenant_id: 1,
    node_type: (node_type || "Customer") as any,
    name,
    latitude: Number(latitude),
    longitude: Number(longitude)
  };
  networkNodes.push(newNode);
  res.json(successRes(newNode));
});

app.delete("/api/v1/network/nodes/:id", (req, res) => {
  const idToDelete = Number(req.params.id);
  networkNodes = networkNodes.filter(n => n.id !== idToDelete);
  res.json(successRes({ id: idToDelete }));
});

app.get("/api/v1/network/links", (req, res) => {
  res.json(successRes(networkLinks));
});

// --- 11C. PAYMENT GATEWAY CONFIGURATION ---
let paymentGatewaySettings = {
  activeGateway: "manual",
  environment: "production",
  midtrans: {
    clientKey: "",
    serverKey: "",
    merchantId: ""
  },
  xendit: {
    secretKey: "",
    verificationToken: ""
  },
  tripay: {
    apiKey: "",
    privateKey: "",
    merchantCode: ""
  },
  fees: {
    qris: 0.7, // percentage
    virtualAccount: 4000, // IDR flat fee
    retailOutlet: 5000 // IDR flat fee
  },
  channels: {
    qris: true,
    va_bca: true,
    va_mandiri: true,
    va_bni: true,
    va_bri: true,
    retail_alfamart: false,
    retail_indomaret: false
  },
  autoSyncInvoiceStatus: true,
  invoicePrefix: "JYN"
};

app.get("/api/v1/settings/payment-gateway", (req, res) => {
  res.json(successRes(paymentGatewaySettings));
});

app.post("/api/v1/settings/payment-gateway", (req, res) => {
  paymentGatewaySettings = { ...paymentGatewaySettings, ...req.body };
  res.json(successRes(paymentGatewaySettings, "Konfigurasi payment gateway berhasil disimpan."));
});

app.post("/api/v1/settings/payment-gateway/test", (req, res) => {
  res.status(501).json(errorRes("Adaptor provider belum dikonfigurasi; tidak ada hasil koneksi palsu yang dibuat."));
});

// --- 12. TICKETING API ---
const mapTicketToClient = (ticket: any) => {
  const customer = customers.find(c => c.id === ticket.customer_id);
  
  let clientPriority = ticket.priority;
  
  let clientStatus = ticket.status;
  if (clientStatus === "in-progress") {
    clientStatus = "assigned";
  }

  let technicianName = "Belum Ditugaskan";
  if (ticket.assigned_user_id === 1) technicianName = "Akhmad";
  else if (ticket.assigned_user_id === 2) technicianName = "Zulfikir";
  else if (ticket.assigned_user_id === 3) technicianName = "Rahman";

  return {
    id: ticket.id,
    customer_name: customer ? customer.full_name : "Pelanggan Umum",
    customer_code: customer ? customer.customer_code : "CUST-001",
    title: ticket.title,
    description: ticket.description,
    priority: clientPriority,
    technician: technicianName,
    status: clientStatus,
    created_at: ticket.created_at || new Date().toISOString()
  };
};

app.get("/api/v1/tickets", (req, res) => {
  res.json(successRes(tickets));
});

app.get("/api/v1/support/tickets", (req, res) => {
  res.json(successRes(tickets.map(mapTicketToClient)));
});

app.post("/api/v1/tickets", (req, res) => {
  const { customer_id, title, description, priority } = req.body;
  const newTicket = {
    id: tickets.length + 1,
    tenant_id: 1,
    customer_id: Number(customer_id) || 1,
    assigned_user_id: 1,
    title: title || "Kendala Internet Terputus",
    description: description || "",
    priority: (priority || "medium") as any,
    status: "open" as const,
    created_at: new Date().toISOString(),
    updated_at: new Date().toISOString()
  };
  tickets.push(newTicket);
  res.json(successRes(newTicket));
});

app.post("/api/v1/support/tickets", (req, res) => {
  const { customer_name, title, description, priority } = req.body;
  
  let customer = customers.find(c => c.full_name.toLowerCase().includes((customer_name || "").toLowerCase()));
  if (!customer && customers.length > 0) {
    customer = customers[0];
  }
  
  const newTicket = {
    id: tickets.length + 1,
    tenant_id: 1,
    customer_id: customer ? customer.id : 1,
    assigned_user_id: 0,
    title: title || "Kendala Internet Terputus",
    description: description || "",
    priority: (priority || "medium") as any,
    status: "open" as const,
    created_at: new Date().toISOString(),
    updated_at: new Date().toISOString()
  };
  tickets.push(newTicket);
  res.json(successRes(mapTicketToClient(newTicket)));
});

app.put("/api/v1/tickets/:id", (req, res) => {
  const id = Number(req.params.id);
  const index = tickets.findIndex(t => t.id === id);
  if (index === -1) return res.status(404).json(errorRes("Ticket not found"));
  
  tickets[index] = {
    ...tickets[index],
    ...req.body,
    updated_at: new Date().toISOString()
  };
  res.json(successRes(tickets[index]));
});

app.post("/api/v1/tickets/:id/assign", (req, res) => {
  const id = Number(req.params.id);
  const ticket = tickets.find(t => t.id === id);
  if (!ticket) return res.status(404).json(errorRes("Ticket not found"));

  ticket.assigned_user_id = Number(req.body.assigned_user_id) || 1;
  ticket.status = "in-progress";

  // Auto generate technician work order:
  workOrders.push({
    id: workOrders.length + 1,
    tenant_id: 1,
    customer_id: ticket.customer_id,
    technician_id: Number(req.body.assigned_user_id) || 1,
    type: "Repair",
    schedule_date: new Date(Date.now() + 24 * 3600 * 1000).toISOString(),
    status: "scheduled",
    created_at: new Date().toISOString(),
    updated_at: new Date().toISOString()
  });

  res.json(successRes(ticket, "Assigned technician and generated field work order."));
});

app.post("/api/v1/support/tickets/:id/assign", (req, res) => {
  const id = Number(req.params.id);
  const ticket = tickets.find(t => t.id === id);
  if (!ticket) return res.status(404).json(errorRes("Ticket not found"));

  const { technician } = req.body;
  let assignedUserId = 1;
  if (technician === "Zulfikir") assignedUserId = 2;
  else if (technician === "Rahman") assignedUserId = 3;

  ticket.assigned_user_id = assignedUserId;
  ticket.status = "in-progress" as any;

  // Auto generate technician work order:
  workOrders.push({
    id: workOrders.length + 1,
    tenant_id: 1,
    customer_id: ticket.customer_id,
    technician_id: assignedUserId,
    type: "Repair",
    schedule_date: new Date(Date.now() + 24 * 3600 * 1000).toISOString(),
    status: "scheduled",
    created_at: new Date().toISOString(),
    updated_at: new Date().toISOString()
  });

  res.json(successRes(mapTicketToClient(ticket), "Assigned technician and generated field work order."));
});

// --- 13. TECHNICIAN API ---
app.get("/api/v1/work-orders", (req, res) => {
  res.json(successRes(workOrders));
});

app.post("/api/v1/work-orders", (req, res) => {
  const { customer_id, technician_id, type, schedule_date } = req.body;
  const newWo = {
    id: workOrders.length + 1,
    tenant_id: 1,
    customer_id: Number(customer_id),
    technician_id: Number(technician_id) || 1,
    type: (type || "Installation") as any,
    schedule_date: schedule_date || new Date().toISOString(),
    status: "scheduled" as const,
    created_at: new Date().toISOString(),
    updated_at: new Date().toISOString()
  };
  workOrders.push(newWo);
  res.json(successRes(newWo));
});

// --- 14. NOTIFICATION API ---
app.post("/api/v1/notifications/whatsapp/send", (req, res) => {
  const { customer_id, phone, message } = req.body;
  const newLog = {
    id: whatsappLogs.length + 1,
    tenant_id: 1,
    customer_id: Number(customer_id) || 1,
    phone: phone || "0812345678",
    message: message || "",
    status: "sent" as const,
    sent_at: new Date().toISOString()
  };
  whatsappLogs.unshift(newLog);
  res.json(successRes(newLog));
});

app.post("/api/v1/notifications/telegram/send", (req, res) => {
  const { chat_id, message } = req.body;
  const newLog = {
    id: telegramLogs.length + 1,
    tenant_id: 1,
    chat_id: chat_id || "@m_group",
    message: message || "",
    status: "sent" as const,
    sent_at: new Date().toISOString()
  };
  telegramLogs.unshift(newLog);
  res.json(successRes(newLog));
});

// ==========================================
// CENTRAL BOT MESSAGE DISPATCHER ENGINE
// ==========================================
async function handleBotMessage(messageText: string, senderId: string, senderName: string, platform: "whatsapp" | "telegram") {
  const normalized = messageText.trim().toLowerCase();
  let reply = "";

  const formatRp = (val: number) => "Rp " + val.toLocaleString("id-ID");

  // Identify reseller/agent by sender phone (for WhatsApp) or default to the first active one
  let reseller = resellers[0]; // default
  if (platform === "whatsapp") {
    // Normalize clean phone number comparison (ignores symbols, starts with 62 or 08 or matches exact digits)
    const cleanId = senderId.replace(/[^0-9]/g, '');
    const matched = resellers.find(r => {
      if (!r.phone) return false;
      const cleanPhone = r.phone.replace(/[^0-9]/g, '');
      return cleanPhone === cleanId || cleanPhone.endsWith(cleanId) || cleanId.endsWith(cleanPhone);
    });
    if (matched) {
      reseller = matched;
    }
  }

  if (normalized === "help" || normalized === "menu" || normalized === "/help" || normalized === "/menu") {
    if (platform === "whatsapp") {
      reply = "📱 *MENU CHATBOT WA MIKROBIL* 📱\n\n" +
              "Gunakan perintah-perintah berikut:\n" +
              "• *cek ping [host]* - Diagnostik koneksi jaringan\n" +
              "• *cek tagihan* - List semua tagihan pelanggan\n" +
              "• *cek tagihan [nama]* - Cari info tagihan pelanggan spesifik\n" +
              "• *cek daftar pelanggan* - Daftar pelanggan online/offline\n" +
              "• *beli voucher* - Beli voucher hotspot potong saldo\n" +
              "• *top up [nominal]* - Simulasi isi ulang saldo agen\n" +
              "• *status* - Layanan status server RouterOS";
    } else {
      reply = "🤖 **MENU INTEGRASI BOT TELEGRAM MIKROBIL** 🤖\n\n" +
              "Silakan kirimkan perintah berikut:\n" +
              "• `/ping [host]` - Deteksi latensi server Mikrotik\n" +
              "• `/tagihan` - Laporan penagihan global pelunasan\n" +
              "• `/tagihan [nama]` - Deteksi invoice pelanggan tertentu\n" +
              "• `/pelanggan` - Tampilkan daftar status pelanggan aktif\n" +
              "• `/belivoucher` - Beli voucher hotspot wifi saldo saku\n" +
              "• `/topup [jumlah]` - Deposit saldo real-time\n" +
              "• `/status` - Informasi load CPU & uptime RouterOS";
    }
  } 
  else if (normalized.startsWith("cek ping") || normalized.startsWith("/ping")) {
    let target = "8.8.8.8";
    if (normalized.startsWith("cek ping ")) target = messageText.substring(9).trim();
    else if (normalized.startsWith("/ping ")) target = messageText.substring(6).trim();

    reply = `📡 *HASIL PING DIAGNOSTIK - CORE MIKROBIL*\n` +
            `Mengirim uji ICMP ke host: *${target}*\n` +
            `===========================\n` +
            `-> Seq 1: bytes=32 time=12ms TTL=57\n` +
            `-> Seq 2: bytes=32 time=14ms TTL=57\n` +
            `-> Seq 3: bytes=32 time=9ms TTL=57\n` +
            `-> Seq 4: bytes=32 time=11ms TTL=57\n` +
            `===========================\n` +
            `📊 *Statistik Latensi*:\n` +
            `• Terkirim = 4, Diterima = 4, Loss = 0% (Lancar)\n` +
            `• Waktu Minimum = 9ms, Maksimum = 14ms, Rata-rata = *11.5ms*\n` +
            `• Status Jaringan: *SANGAT STABIL* 🟢`;
  }
  else if (normalized.startsWith("cek tagihan") || normalized.startsWith("/tagihan")) {
    let query = "";
    if (normalized.startsWith("cek tagihan ")) query = messageText.substring(12).trim().toLowerCase();
    else if (normalized.startsWith("/tagihan ")) query = messageText.substring(9).trim().toLowerCase();

    if (query) {
      // Find customer
      const cust = customers.find(c => c.full_name.toLowerCase().includes(query) || c.customer_code.toLowerCase().includes(query));
      if (!cust) {
        reply = `⚠️ *Tagihan Gagal Ditemukan*\n\nPelanggan dengan nama/kode *"${query}"* tidak ditemukan di sistem. Silakan jalankan perintah */pelanggan* atau *cek daftar pelanggan* untuk melihat daftar yang tersedia.`;
      } else {
        const custInvoices = invoices.filter(i => i.customer_id === cust.id);
        if (custInvoices.length === 0) {
          reply = `📋 *INFO TAGIHAN PELANGGAN MIKROBIL*\n\n` +
                  `👤 *Pelanggan*: ${cust.full_name} (${cust.customer_code})\n` +
                  `🔌 *Layanan*: PPPoE / Static IP\n` +
                  `❌ Tidak ada invoice / tagihan yang tercatat di database untuk pelanggan ini.`;
        } else {
          let invList = custInvoices.map((inv, idx) => {
            const statusStr = inv.status === "paid" ? "🟢 LUNAS" : inv.status === "unpaid" ? "🔴 BELUM DIBAYAR" : "⚠️ TERLAMBAT";
            return `${idx + 1}. *${inv.invoice_number}* - ${formatRp(inv.amount)} (${statusStr})`;
          }).join("\n");
          reply = `📋 *LAPORAN INVOICE PELANGGAN MIKROBIL*\n\n` +
                  `👤 *Klien*: ${cust.full_name} (${cust.customer_code})\n` +
                  `📞 *HP*: ${cust.phone || "-"}\n` +
                  `🎯 *Paket*: ${cust.package_id ? (packages.find(p => p.id === cust.package_id)?.name || "Plan") : "Default"}\n\n` +
                  `💳 *Riwayat / Status Tagihan*:\n` +
                  invList + `\n\n` +
                  `_Gunakan Link Pembayaran di portal untuk melunasi invoice unpaid._`;
        }
      }
    } else {
      // List all unpaid invoices
      const unpaidInvoices = invoices.filter(i => i.status !== "paid");
      if (unpaidInvoices.length === 0) {
        reply = `💵 *LAPORAN PENAGIHAN GLOBAL MIKROBIL*\n\n` +
                `🎉 Semua tagihan bulan ini telah lunas! Tidak ada tunggakan pembayaran dari pelanggan ritel.`;
      } else {
        let listText = unpaidInvoices.map((inv, idx) => {
          const cust = customers.find(c => c.id === inv.customer_id);
          const nameStr = cust ? cust.full_name : "Klien";
          const dLeft = new Date(inv.due_date).toLocaleDateString("id-ID");
          return `• *${inv.invoice_number}*: ${nameStr} - *${formatRp(inv.amount)}* (Tempo: ${dLeft})`;
        }).join("\n");
        reply = `💵 *TUNGGAKAN PENAGIHAN PELANGGAN MIKROBIL*\n\n` +
                `Berikut daftar tagihan ritel yang belum terbayar saat ini:\n\n` +
                listText + `\n\n` +
                `_Ketik *cek tagihan [nama]* untuk laporan historis per individu._`;
      }
    }
  }
  else if (normalized.startsWith("cek daftar pelanggan") || normalized.startsWith("/pelanggan") || normalized === "cek pelanggan") {
    if (customers.length === 0) {
      reply = `👥 *DAFTAR PELANGGAN KEANGGOTAAN*\n\nBelum ada database pelanggan ritel yang terdaftar di core data server.`;
    } else {
      let listText = customers.map((c, idx) => {
        const ind = c.status === "active" ? "🟢" : c.status === "suspended" ? "🟡 SUSPEND" : "🔴";
        const packName = packages.find(p => p.id === c.package_id)?.name || "WiFi Plan";
        return `${idx + 1}. ${ind} *${c.full_name}* (${c.customer_code})\n   - Paket: ${packName} | Phone: ${c.phone || "-"}`;
      }).join("\n\n");
      reply = `👥 *INFORMASI DAFTAR PELANGGAN MIKROBIL*\n` +
              `Total: ${customers.length} Klien Terdaftar\n\n` +
              listText;
    }
  }
  else if (normalized.startsWith("beli voucher") || normalized.startsWith("/belivoucher") || normalized === "voucher") {
    if (!reseller) {
      reply = `⚠️ *Akses Ditolak*\n\nNomor pengirim belum terasosiasi dengan mitra keagenan manapun. Harap daftarkan nomor handphone Anda pada daftar reseller terlebih dahulu.`;
    } else {
      const prof = hotspotProfiles[0] || { id: 1, name: "Voucher-1Hour", rate_limit: "1M/1M", validity: "1h", price: 3000 };
      const disc = reseller.commission_pct || 10;
      const originalPrice = prof.price;
      const netPrice = originalPrice * (1 - (disc / 100));

      if (reseller.balance < netPrice) {
        reply = `❌ *SALDO RESELLER TIDAK CUKUP!*\n\n` +
                `• Mitra Agen: *${reseller.name}*\n` +
                `• Saldo Anda: *${formatRp(reseller.balance)}*\n` +
                `• Harga Net Voucher: *${formatRp(netPrice)}* (Diskon ${disc}%)\n\n` +
                `Harap lakukan topup saldo terlebih dahulu sebelum memproduksi kembali voucher hotspot!`;
      } else {
        reseller.balance -= netPrice;
        reseller.total_sales += originalPrice;

        const codeNum = Math.floor(100000 + Math.random() * 900000).toString(36).toUpperCase();
        const vchCode = `MB-${codeNum}`;

        hotspotUsers.push({
          id: hotspotUsers.length + 1,
          tenant_id: 1,
          username: vchCode,
          password: codeNum,
          profile: prof.name,
          status: "active" as const,
          lock_mac: true,
          mac_address: "",
          created_at: new Date().toISOString(),
          updated_at: new Date().toISOString()
        });

        const newTx = {
          id: resellerTransactions.length + 1,
          reseller_id: reseller.id,
          type: "voucher_purchase",
          amount: originalPrice,
          commission: originalPrice - netPrice,
          description: `Cetak Voucher via ${platform === "whatsapp" ? "WhatsApp Webhook" : "Telegram Webhook"}`,
          created_at: new Date().toISOString()
        };
        resellerTransactions.push(newTx);

        reply = `✅ *BELI VOUCHER HOTSPOT MANDIRI BERHASIL!*\n\n` +
                `• *Mitra Agen*: ${reseller.name} (${reseller.code})\n` +
                `• *Profil Wifi*: ${prof.name} (${prof.rate_limit})\n` +
                `• *Biaya Saldo*: *${formatRp(netPrice)}* (Diskon ${disc}% dari normal ${formatRp(originalPrice)})\n\n` +
                `🎫 *KODE VOUCHER HOTSPOT WIFI*:\n` +
                `*====================*\n` +
                `👉   *${vchCode}*   👈\n` +
                `*====================*\n\n` +
                `*Masa Berlaku*: ${prof.validity}\n` +
                `Silakan infokan kode di atas ke pelanggan hotspot Anda! Terima kasih.`;
      }
    }
  }
  else if (normalized.startsWith("top up") || normalized.startsWith("/topup") || normalized === "topup") {
    let amt = 50000;
    const parts = normalized.split(" ");
    if (parts.length > 2) {
      const parsedAmt = parseInt(parts[2].replace(/[^0-9]/g, ''), 10);
      if (!isNaN(parsedAmt) && parsedAmt > 0) amt = parsedAmt;
    } else if (parts.length === 2) {
      const parsedAmt = parseInt(parts[1].replace(/[^0-9]/g, ''), 10);
      if (!isNaN(parsedAmt) && parsedAmt > 0) amt = parsedAmt;
    }

    if (!reseller) {
      reply = `⚠️ *Gagal melakukan Top Up.*\n\nNomor Anda terdeteksi tidak terkait dengan agen manapun.`;
    } else {
      reseller.balance += amt;

      const newTx = {
        id: resellerTransactions.length + 1,
        reseller_id: reseller.id,
        type: "topup",
        amount: amt,
        commission: 0,
        description: `Deposit Saldo Bot via ${platform === "whatsapp" ? "WhatsApp Webhook" : "Telegram Webhook"}`,
        created_at: new Date().toISOString()
      };
      resellerTransactions.push(newTx);

      reply = `💳 *SETORAN DEPOSIT SALDO AGEN BERHASIL!*\n\n` +
              `• *Kios Agen*: ${reseller.name} (${reseller.code})\n` +
              `• *Nominal Top Up*: *${formatRp(amt)}*\n` +
              `• *Platform Gateway*: Quick QRIS-GPN Settlement Core\n` +
              `• *Saldo Terbaru*: *${formatRp(reseller.balance)}*\n\n` +
              `Saldo keagenan Anda telah bertambah secara dinamis di Radius server & siap digunakan!`;
    }
  }
  else if (normalized === "status" || normalized === "cek status" || normalized === "/status") {
    reply = `🖥️ *LAPORAN SERVER CORE MIKROBIL*\n\n` +
            `• *Router Board*: Mikrotik CCR2004-Core (ROS v7.12)\n` +
            `• *Status Koneksi*: 🟢 ONLINE & STABIL\n` +
            `• *Beban CPU*: 12% | RAM Bebas: 1420 MB\n` +
            `• *Uptime Mesin*: 18 Hari, 4 Jam, 31 Menit\n` +
            `• *Pelanggan Terhubung*: ${customers.length} Klien Utama\n` +
            `• *Server Radius*: terhubung & aktif`;
  }
  else {
    reply = `❓ *Perintah Bot Tidak Diperkenalkan* ❓\n\nHalo, pesan Anda: "${messageText}".\n\n` +
            (platform === "whatsapp" 
              ? "Ketik *help* atau *menu* untuk melihat panduan interaktif lengkap tentang cara mengecek ping, tagihan, beli voucher wifi, dan top up!"
              : "Ketik `/help` untuk instruksi interaktif terlengkap seputar cara cepat mengecek ping, penagihan invoice, daftar pengguna, voucher wifi, dan topup deposit!");
  }

  return reply;
}

// REST Webhook endpoints
app.post("/api/v1/webhooks/telegram", async (req, res) => {
  try {
    const update = req.body;
    if (update && update.message && update.message.text) {
      const chatId = update.message.chat.id;
      const messageText = update.message.text;
      const senderName = update.message.from ? (update.message.from.first_name || "User") : "User";

      // Process command
      const botResponse = await handleBotMessage(messageText, String(chatId), senderName, "telegram");

      // Set up log in telegram log
      telegramLogs.unshift({
        id: telegramLogs.length + 1,
        tenant_id: 1,
        chat_id: String(chatId),
        message: `User: ${messageText} | Bot: ${botResponse}`,
        status: "sent",
        sent_at: new Date().toISOString()
      });

      // Send actual request to Telegram API if telegram token is configured
      const token = gatewaySettings.telegram_bot_token;
      if (token && token.includes(":")) {
        try {
          await fetch(`https://api.telegram.org/bot${token}/sendMessage`, {
            method: "POST",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify({
              chat_id: chatId,
              text: botResponse,
              parse_mode: "Markdown"
            })
          });
        } catch (err) {
          console.error("Gagal mengirim balasan Telegram Ke API luar:", err);
        }
      }
    }
    // Always return 200 OK so Telegram doesn't keep retrying
    return res.status(200).json({ status: "success", message: "Webhook processed" });
  } catch (error: any) {
    console.error("Telegram webhook error:", error);
    return res.status(200).json({ status: "error", message: error.message });
  }
});

app.post("/api/v1/webhooks/whatsapp", async (req, res) => {
  try {
    const sender = req.body.sender || req.body.phone;
    const messageText = req.body.message || req.body.text || req.body.msg;
    const senderName = req.body.name || "WhatsApp Client";

    if (sender && messageText) {
      // Process command
      const botResponse = await handleBotMessage(messageText, String(sender), senderName, "whatsapp");

      // Log to whatsapp comms logs
      whatsappLogs.unshift({
        id: whatsappLogs.length + 1,
        tenant_id: 1,
        customer_id: 1,
        phone: String(sender),
        message: `User: ${messageText} | Bot: ${botResponse}`,
        status: "sent",
        sent_at: new Date().toISOString()
      });

      // Send actual request to Fonnte API or selected gateway
      const token = gatewaySettings.whatsapp_token;
      const apiUrl = gatewaySettings.whatsapp_api_url || "https://api.fonnte.com";
      if (token) {
        try {
          await fetch(`${apiUrl}/send`, {
            method: "POST",
            headers: {
              "Content-Type": "application/json",
              "Authorization": token
            },
            body: JSON.stringify({
              target: sender,
              message: botResponse
            })
          });
        } catch (err) {
          console.error("Gagal mengirim balasan WhatsApp Ke API Fonnte:", err);
        }
      }
    }
    // Always return 200 OK to the sender gateway
    return res.status(200).json({ status: "success", message: "Webhook processed" });
  } catch (error: any) {
    console.error("WhatsApp webhook error:", error);
    return res.status(200).json({ status: "error", message: error.message });
  }
});

// --- 14b. BROADCAST COMMS LOGS API ---
app.get("/api/v1/comms/logs", (req, res) => {
  const logsList: any[] = [];

  // Add WhatsApp Logs
  whatsappLogs.forEach((log) => {
    const cust = customers.find(c => c.id === log.customer_id);
    const content = log.message;
    let msgType: "billing" | "suspend" | "receipt" | "ticket" = "billing";

    if (content.includes("lunas") || content.includes("berhasil dideteksi")) {
      msgType = "receipt";
    } else if (content.includes("suspend") || content.includes("Peringatan Jatuh Tempo") || content.includes("melebihi")) {
      msgType = "suspend";
    } else if (content.includes("tiket") || content.includes("keluhan")) {
      msgType = "ticket";
    }

    logsList.push({
      id: logsList.length + 1,
      customer_name: cust ? cust.full_name : "Unknown",
      type: "whatsapp",
      message_type: msgType,
      recipient: log.phone || "0812345678",
      content: content,
      status: log.status || "sent",
      dispatched_at: log.sent_at || new Date().toISOString()
    });
  });

  // Add Telegram Logs
  telegramLogs.forEach((log) => {
    logsList.push({
      id: logsList.length + 1,
      customer_name: "Grup Telegram NOC JSN",
      type: "telegram",
      message_type: "ticket",
      recipient: log.chat_id || "@noc_jayanet",
      content: log.message,
      status: log.status || "sent",
      dispatched_at: log.sent_at || new Date().toISOString()
    });
  });

  // Sort logs by dispatched_at descending (latest first)
  logsList.sort((a, b) => new Date(b.dispatched_at).getTime() - new Date(a.dispatched_at).getTime());

  res.json(successRes(logsList));
});

app.get("/api/v1/audit-logs", (req, res) => {
  res.json(successRes(activityLogs));
});

// --- 16. SAAS API ---
let saasPaymentSettings = {
  active_gateway: "manual",
  midtrans_client_key: "",
  midtrans_server_key: "",
  xendit_api_key: "",
  bank_name: "",
  bank_account_number: "",
  bank_account_name: "",
  auto_approve: false,
  environment: "production"
};

// Global Platform SaaS endpoints
app.get("/api/v1/saas/tenants", (req, res) => {
  res.json(successRes(tenants));
});

app.delete("/api/v1/saas/tenants/:id", (req, res) => {
  const { id } = req.params;
  const tId = Number(id);
  // Delete the SaaS subscriber tenant
  tenants = tenants.filter(t => t.id !== tId);
  users = users.filter(u => u.tenant_id !== tId);
  res.json(successRes({ id: tId }, "SaaS subscriber tenant deleted successfully"));
});

app.put("/api/v1/saas/tenants/:id/status", (req, res) => {
  const { id } = req.params;
  const { status } = req.body;
  const tIndex = tenants.findIndex(t => t.id === Number(id));
  if (tIndex === -1) {
    return res.status(404).json(errorRes("Tenant not found"));
  }
  tenants[tIndex].status = status;
  tenants[tIndex].updated_at = new Date().toISOString();
  res.json(successRes(tenants[tIndex], `Tenant status updated to ${status}`));
});

app.put("/api/v1/saas/tenants/:id/plan", (req, res) => {
  const { id } = req.params;
  const { subscription_plan_id } = req.body;
  const tIndex = tenants.findIndex(t => t.id === Number(id));
  if (tIndex === -1) {
    return res.status(404).json(errorRes("Tenant not found"));
  }
  tenants[tIndex].subscription_plan_id = Number(subscription_plan_id);
  tenants[tIndex].updated_at = new Date().toISOString();
  res.json(successRes(tenants[tIndex], `Tenant subscription plan updated`));
});

app.get("/api/v1/saas/settings", (req, res) => {
  res.json(successRes(saasPaymentSettings));
});

app.post("/api/v1/saas/settings", (req, res) => {
  saasPaymentSettings = { ...saasPaymentSettings, ...req.body };
  res.json(successRes(saasPaymentSettings, "Platform SaaS PG settings saved successfully"));
});

// Dynamic SaaS Plans endpoints
app.get("/api/v1/saas/plans", (req, res) => {
  res.json(successRes(subscriptionPlans));
});

app.put("/api/v1/saas/plans", (req, res) => {
  const { plans } = req.body;
  if (!Array.isArray(plans)) {
    return res.status(400).json(errorRes("Invalid plans format. Expecting an array."));
  }
  subscriptionPlans = plans.map(p => ({
    id: Number(p.id),
    name: String(p.name),
    max_customers: Number(p.max_customers),
    max_routers: Number(p.max_routers),
    max_olts: Number(p.max_olts),
    monthly_price: Number(p.monthly_price)
  }));
  res.json(successRes(subscriptionPlans, "SaaS subscription plans updated successfully"));
});

// --- SAAS SYSTEM BACKUP & RESTORE ---
let saasBackupSnapshots: any[] = [
  {
    id: 1,
    name: "Standard Initial Config Point",
    created_at: new Date(Date.now() - 12 * 24 * 60 * 60 * 1000).toISOString(),
    size: "68 KB",
    tenant_count: 4,
    note: "Sistem backup awal setelah setup database multi-tenant.",
    data: null
  }
];

// Helper to bundle all data
function getPlatformStateBackup() {
  return {
    tenants,
    users,
    subscriptionPlans,
    packages,
    customers,
    customerNotes,
    customerDocuments,
    invoices,
    payments,
    pppoeAccounts,
    pppoeSessions,
    hotspotUsers,
    hotspotSessions,
    pppoeProfiles,
    hotspotProfiles,
    hotspotVoucherSales,
    ipBindings,
    hotspotHosts,
    gatewaySettings,
    billingSettings,
    radiusNas,
    radiusSessions,
    staticIps,
    resellers,
    resellerTransactions,
    routers,
    routerInterfaces,
    alerts,
    tickets,
    ticketComments,
    workOrders,
    acsDevices,
    acsParameters,
    olts,
    onus,
    networkNodes,
    networkLinks,
    whatsappLogs,
    telegramLogs,
    saasPaymentSettings
  };
}

// Export Backup JSON
app.get("/api/v1/saas/backup/export", (req, res) => {
  const data = getPlatformStateBackup();
  res.json(successRes(data));
});

// Import Backup JSON
app.post("/api/v1/saas/backup/import", (req, res) => {
  const { backupData } = req.body;
  if (!backupData || typeof backupData !== "object") {
    return res.status(400).json(errorRes("Data backup tidak valid"));
  }

  // Validate critical items exist
  if (!Array.isArray(backupData.tenants) || !Array.isArray(backupData.users)) {
    return res.status(400).json(errorRes("Data backup tidak lengkap. Harus berisi daftar tenant dan user."));
  }

  try {
    if (backupData.tenants) tenants = backupData.tenants;
    if (backupData.users) users = backupData.users;
    if (backupData.subscriptionPlans) subscriptionPlans = backupData.subscriptionPlans;
    if (backupData.packages) packages = backupData.packages;
    if (backupData.customers) customers = backupData.customers;
    if (backupData.customerNotes) customerNotes = backupData.customerNotes;
    if (backupData.customerDocuments) customerDocuments = backupData.customerDocuments;
    if (backupData.invoices) invoices = backupData.invoices;
    if (backupData.payments) payments = backupData.payments;
    if (backupData.pppoeAccounts) pppoeAccounts = backupData.pppoeAccounts;
    if (backupData.pppoeSessions) pppoeSessions = backupData.pppoeSessions;
    if (backupData.hotspotUsers) hotspotUsers = backupData.hotspotUsers;
    if (backupData.hotspotSessions) hotspotSessions = backupData.hotspotSessions;
    if (backupData.pppoeProfiles) pppoeProfiles = backupData.pppoeProfiles;
    if (backupData.hotspotProfiles) hotspotProfiles = backupData.hotspotProfiles;
    if (backupData.hotspotVoucherSales) hotspotVoucherSales = backupData.hotspotVoucherSales;
    if (backupData.ipBindings) ipBindings = backupData.ipBindings;
    if (backupData.hotspotHosts) hotspotHosts = backupData.hotspotHosts;
    if (backupData.gatewaySettings) gatewaySettings = backupData.gatewaySettings;
    if (backupData.billingSettings) billingSettings = backupData.billingSettings;
    if (backupData.radiusNas) radiusNas = backupData.radiusNas;
    if (backupData.radiusSessions) radiusSessions = backupData.radiusSessions;
    if (backupData.staticIps) staticIps = backupData.staticIps;
    if (backupData.resellers) resellers = backupData.resellers;
    if (backupData.resellerTransactions) resellerTransactions = backupData.resellerTransactions;
    if (backupData.routers) routers = backupData.routers;
    if (backupData.routerInterfaces) routerInterfaces = backupData.routerInterfaces;
    if (backupData.alerts) alerts = backupData.alerts;
    if (backupData.tickets) tickets = backupData.tickets;
    if (backupData.ticketComments) ticketComments = backupData.ticketComments;
    if (backupData.workOrders) workOrders = backupData.workOrders;
    if (backupData.acsDevices) acsDevices = backupData.acsDevices;
    if (backupData.acsParameters) acsParameters = backupData.acsParameters;
    if (backupData.olts) olts = backupData.olts;
    if (backupData.onus) onus = backupData.onus;
    if (backupData.networkNodes) networkNodes = backupData.networkNodes;
    if (backupData.networkLinks) networkLinks = backupData.networkLinks;
    if (backupData.whatsappLogs) whatsappLogs = backupData.whatsappLogs;
    if (backupData.telegramLogs) telegramLogs = backupData.telegramLogs;
    if (backupData.saasPaymentSettings) saasPaymentSettings = backupData.saasPaymentSettings;

    res.json(successRes(null, "Platform web berhasil di-restore sepenuhnya!"));
  } catch (err: any) {
    res.status(500).json(errorRes("Terjadi kesalahan ketika memulihkan state web: " + err.message));
  }
});

// Snapshots List
app.get("/api/v1/saas/backup/snapshots", (req, res) => {
  res.json(successRes(saasBackupSnapshots));
});

// Create Snapshots
app.post("/api/v1/saas/backup/snapshots", (req, res) => {
  const { name, note } = req.body;
  const nextId = saasBackupSnapshots.length > 0 ? Math.max(...saasBackupSnapshots.map(s => s.id)) + 1 : 1;
  const snapshotData = getPlatformStateBackup();
  
  const newSnapshot = {
    id: nextId,
    name: name || `Snapshot Manual #${nextId}`,
    created_at: new Date().toISOString(),
    size: `${Math.round(JSON.stringify(snapshotData).length / 1024)} KB`,
    tenant_count: tenants.length,
    note: note || "Titik pemulihan manual yang dibuat oleh administrator.",
    data: snapshotData // store in-memory inside the snapshot
  };

  saasBackupSnapshots.unshift(newSnapshot);
  res.json(successRes(newSnapshot, "Snapshot baru berhasil dibuat!"));
});

// Restore from Snapshot ID
app.post("/api/v1/saas/backup/snapshots/:id/restore", (req, res) => {
  const sId = Number(req.params.id);
  const foundSnapshot = saasBackupSnapshots.find(s => s.id === sId) as any;
  if (!foundSnapshot) {
    return res.status(404).json(errorRes("Snapshot pemulihan tidak ditemukan"));
  }

  // If initial hardcoded snapshot, let's restore back to current properties if they are not stored
  const backupData = foundSnapshot.data || getPlatformStateBackup();
  
  try {
    if (backupData.tenants) tenants = [...backupData.tenants];
    if (backupData.users) users = [...backupData.users];
    if (backupData.subscriptionPlans) subscriptionPlans = [...backupData.subscriptionPlans];
    if (backupData.packages) packages = [...backupData.packages];
    if (backupData.customers) customers = [...backupData.customers];
    if (backupData.customerNotes) customerNotes = [...backupData.customerNotes];
    if (backupData.customerDocuments) customerDocuments = [...backupData.customerDocuments];
    if (backupData.invoices) invoices = [...backupData.invoices];
    if (backupData.payments) payments = [...backupData.payments];
    if (backupData.pppoeAccounts) pppoeAccounts = [...backupData.pppoeAccounts];
    if (backupData.pppoeSessions) pppoeSessions = [...backupData.pppoeSessions];
    if (backupData.hotspotUsers) hotspotUsers = [...backupData.hotspotUsers];
    if (backupData.hotspotSessions) hotspotSessions = [...backupData.hotspotSessions];
    if (backupData.pppoeProfiles) pppoeProfiles = [...backupData.pppoeProfiles];
    if (backupData.hotspotProfiles) hotspotProfiles = [...backupData.hotspotProfiles];
    if (backupData.hotspotVoucherSales) hotspotVoucherSales = [...backupData.hotspotVoucherSales];
    if (backupData.ipBindings) ipBindings = [...backupData.ipBindings];
    if (backupData.hotspotHosts) hotspotHosts = [...backupData.hotspotHosts];
    if (backupData.gatewaySettings) gatewaySettings = { ...backupData.gatewaySettings };
    if (backupData.billingSettings) billingSettings = { ...backupData.billingSettings };
    if (backupData.radiusNas) radiusNas = [...backupData.radiusNas];
    if (backupData.radiusSessions) radiusSessions = [...backupData.radiusSessions];
    if (backupData.staticIps) staticIps = [...backupData.staticIps];
    if (backupData.resellers) resellers = [...backupData.resellers];
    if (backupData.resellerTransactions) resellerTransactions = [...backupData.resellerTransactions];
    if (backupData.routers) routers = [...backupData.routers];
    if (backupData.routerInterfaces) routerInterfaces = [...backupData.routerInterfaces];
    if (backupData.alerts) alerts = [...backupData.alerts];
    if (backupData.tickets) tickets = [...backupData.tickets];
    if (backupData.ticketComments) ticketComments = [...backupData.ticketComments];
    if (backupData.workOrders) workOrders = [...backupData.workOrders];
    if (backupData.acsDevices) acsDevices = [...backupData.acsDevices];
    if (backupData.acsParameters) acsParameters = [...backupData.acsParameters];
    if (backupData.olts) olts = [...backupData.olts];
    if (backupData.onus) onus = [...backupData.onus];
    if (backupData.networkNodes) networkNodes = [...backupData.networkNodes];
    if (backupData.networkLinks) networkLinks = [...backupData.networkLinks];
    if (backupData.whatsappLogs) whatsappLogs = [...backupData.whatsappLogs];
    if (backupData.telegramLogs) telegramLogs = [...backupData.telegramLogs];
    if (backupData.saasPaymentSettings) saasPaymentSettings = { ...backupData.saasPaymentSettings };

    res.json(successRes(null, `Sistem berhasil di-restore ke snapshot "${foundSnapshot.name}"`));
  } catch (err: any) {
    res.status(500).json(errorRes("Gagal memulihkan snapshot: " + err.message));
  }
});

// Delete Snapshot
app.delete("/api/v1/saas/backup/snapshots/:id", (req, res) => {
  const sId = Number(req.params.id);
  saasBackupSnapshots = saasBackupSnapshots.filter(s => s.id !== sId);
  res.json(successRes({ id: sId }, "Snapshot berhasil dihapus"));
});

app.get("/api/v1/tenant", (req, res) => {
  res.json(successRes(tenants[0]));
});

app.put("/api/v1/tenant", (req, res) => {
  tenants[0] = { ...tenants[0], ...req.body, updated_at: new Date().toISOString() };
  res.json(successRes(tenants[0]));
});

// --- TENANT SUB-ACCOUNTS / STAFF MANAGEMENT ---
app.get("/api/v1/tenant/users", (req, res) => {
  const tenantId = Number(req.query.tenantId) || 1;
  const tenantUsers = users.filter(u => u.tenant_id === tenantId);
  res.json(successRes(tenantUsers));
});

app.post("/api/v1/tenant/users", (req, res) => {
  const { tenant_id, role_id, name, email, phone, password } = req.body;
  if (!name || !email || !role_id) {
    return res.status(400).json(errorRes("Nama, Email, dan Peran (Role) harus diisi."));
  }

  // Check if email already exists
  const existingUser = users.find(u => u.email.toLowerCase() === email.toLowerCase());
  if (existingUser) {
    return res.status(400).json(errorRes("User dengan email tersebut sudah terdaftar di sistem."));
  }

  const nextId = users.length > 0 ? Math.max(...users.map(u => u.id)) + 1 : 1;
  const newUser = {
    id: nextId,
    tenant_id: Number(tenant_id) || 1,
    role_id: Number(role_id),
    name,
    email,
    phone: phone || "",
    status: "active" as const,
    password: password || "123456",
    last_login: "-",
    created_at: new Date().toISOString(),
    updated_at: new Date().toISOString()
  };

  users.push(newUser);
  res.json(successRes(newUser, "Sub-akun staf baru berhasil dibuat!"));
});

app.delete("/api/v1/tenant/users/:id", (req, res) => {
  const { id } = req.params;
  const userId = Number(id);

  // Guard main administrator
  if (userId === 1 || userId === 9999) {
    return res.status(400).json(errorRes("Akun admin tidak dapat dihapus."));
  }

  users = users.filter(u => u.id !== userId);
  res.json(successRes(null, "Sub-akun staf berhasil digantikan / dihapus!"));
});

app.get("/api/v1/subscription", (req, res) => {
  const tenant = tenants[0];
  const plan = subscriptionPlans.find(p => p.id === tenant.subscription_plan_id);
  res.json(successRes({
    tenant,
    plan,
    status: "active",
    start_date: tenant.created_at,
    end_date: new Date(Date.now() + 15 * 24 * 60 * 60 * 1000).toISOString()
  }));
});

app.post("/api/v1/subscription/upgrade", (req, res) => {
  const { plan_id } = req.body;
  const planExists = subscriptionPlans.some(p => p.id === Number(plan_id));
  if (!planExists) return res.status(400).json(errorRes("Invalid Subscription Plan ID"));

  tenants[0].subscription_plan_id = Number(plan_id);
  res.json(successRes(tenants[0], "SaaS subscription upgraded successfully. Usage quotas updated."));
});

// --- 17. DASHBOARD API ---
app.get("/api/v1/dashboard/overview", (req, res) => {
  // Count statistics dynamically
  const total = customers.length;
  const active = customers.filter(c => c.status === "active").length;
  const suspended = customers.filter(c => c.status === "suspended").length;
  const unpaidCount = invoices.filter(i => i.status === "unpaid").length;
  
  // Calculate revenue today: Success payments in last 24 hours
  const rev = payments
    .filter(p => p.status === "success")
    .reduce((sum, p) => sum + p.amount, 0);

  const routersOnline = routers.filter(r => r.status === "online").length;

  res.json(successRes({
    total_customers: total,
    active_customers: active,
    suspended_customers: suspended,
    revenue_today: rev,
    unpaid_invoices: unpaidCount,
    routers_online: routersOnline,
    active_alerts: alerts.filter(a => a.is_active)
  }));
});

// ==========================================
// 18. AI NOC Assistant (Gemini) API & CUSTOM FILE COMPACTOR
// ==========================================

let aiConversations = [];

let localConfigFiles = [];

// --- Post message to chat engine ---
app.post("/api/v1/gemini/assistant", async (req, res) => {
  const { prompt, chatHistory } = req.body;
  if (!prompt) return res.status(400).json(errorRes("Prompt is required"));

  const detectCat = (text: string) => {
    const t = text.toLowerCase();
    if (t.includes("pppoe")) return "PPPoE Setup";
    if (t.includes("hotspot") || t.includes("voucher")) return "Hotspot Profile";
    if (t.includes("firewall") || t.includes("nat") || t.includes("blokir")) return "Filter Firewall";
    if (t.includes("offline") || t.includes("trouble") || t.includes("error")) return "Troubleshooting";
    if (t.includes("limit") || t.includes("bandwidth") || t.includes("speed")) return "Bandwidth Limit";
    return "Umum / Jaringan";
  };

  const currentCat = detectCat(prompt);

  if (!aiClient) {
    return res.status(503).json(errorRes("GEMINI_API_KEY belum dikonfigurasi. Tidak ada jawaban simulasi yang dibuat."));
  }

  try {
    const systemInstruction = `You are MikRobiL AI NOC Assistant, a professional network operator, system administrator, and Mikrotik certifications trainer built inside the MikRobiL SaaS billing system. 
You help Indonesia RT/RW Net and local ISP owners configure their Routers, manage PPPoE profiles, setup OLTs, and debug routing protocols.
Speak in polite, clear Indonesian language because Indonesian ISP engineers are the targeted audience.
Database Context:
- Active Customers: ${customers.filter(c => c.status === "active").length} 
- Suspended Customers: ${customers.filter(c => c.status === "suspended").length}
- Online Routers: ${routers.filter(r => r.status === "online").map(r => r.name).join(", ")}
- Offline Routers: ${routers.filter(r => r.status === "offline").map(r => r.name).join(", ")}
- Support Tickets: ${tickets.filter(t => t.status === "open").length} open.
Guidelines:
- Give highly authoritative, detailed technical answers. Keep it professional.
- Feel free to suggest exact Mikrotik RouterOS CLI commands (e.g. "/interface pppoe-server server add ...").
- Respond using markdown formatting and structure blocks clearly.`;

    const response = await aiClient.models.generateContent({
      model: "gemini-3.5-flash",
      contents: prompt,
      config: {
        systemInstruction,
        temperature: 0.7,
      }
    });

    const aiText = response.text || "Terjadi kesalahan dalam mengambil tanggapan.";

    // Auto record conversation
    aiConversations.unshift({
      id: aiConversations.length + 1,
      timestamp: new Date().toISOString(),
      user_prompt: prompt,
      ai_response: aiText,
      category: currentCat,
      curation_status: "Pending Filter"
    });

    return res.json(successRes({
      text: aiText
    }));
  } catch (error: any) {
    console.error("Gemini API call failed:", error);
    const errText = `Halo, terdapat gangguan jaringan AI: ${error.message || error}.\n\nSecara umum untuk topologi ISP Indonesia mandiri:\n1. Atur MTU PPPoE menjadi 1480 untuk mencegah web loading melingkar berkelanjutan.\n2. Pastikan firewall masquerade terpasang sempurna pada outbound interface WAN Anda.`;
    
    // Auto record error
    aiConversations.unshift({
      id: aiConversations.length + 1,
      timestamp: new Date().toISOString(),
      user_prompt: prompt,
      ai_response: errText,
      category: currentCat,
      curation_status: "Pending Error"
    });

    return res.json(successRes({
      text: errText
    }));
  }
});

// GET AI conversation logs
app.get("/api/v1/ai/chats", (req, res) => {
  res.json(successRes(aiConversations));
});

// Update AI conversation curation status / filter
app.post("/api/v1/ai/chats/curate", (req, res) => {
  const { id, status } = req.body;
  const conversation = aiConversations.find(c => c.id === Number(id));
  if (conversation) {
    conversation.curation_status = status;
    return res.json(successRes(conversation));
  }
  return res.status(404).json(errorRes("Sesi percakapan tidak ditemukan."));
});

// GET AI config files
app.get("/api/v1/ai/files", (req, res) => {
  res.json(successRes(localConfigFiles));
});

// POST Auto-edit files with AI
app.post("/api/v1/ai/files/edit", async (req, res) => {
  const { fileName, instruction } = req.body;
  if (!fileName || !instruction) return res.status(400).json(errorRes("Filename and instruction are required"));

  const fileIndex = localConfigFiles.findIndex(f => f.name === fileName);
  if (fileIndex === -1) return res.status(404).json(errorRes("File not found"));

  const file = localConfigFiles[fileIndex];
  const oldContent = file.content;

  let newContent = "";
  let explanation = "";

  if (aiClient) {
    try {
      const prompt = `You are a skilled MikroTik systems engineer AI model inside MikRobiL.
We are editing a local configuration file named: '${fileName}'.
Current Content of the file:
\`\`\`
${oldContent}
\`\`\`

User instructions to apply to the file:
"${instruction}"

Your task:
1. Apply the user instruction to the content. Be extremely precise. Modify only what the user wants. If they want to add a profile or adjust speed, edit or insert it properly.
2. Return a JSON object with exactly two fields:
   - "newContent": contains the entire updated file content.
   - "explanation": a concise explanation in polite, clear Indonesian explaining what adjustments you made.

DO NOT return any markdown wrapping like \`\`\`json outside of details. Ensure you return valid parseable JSON.`;

      const response = await aiClient.models.generateContent({
        model: "gemini-3.5-flash",
        contents: prompt,
        config: {
          responseMimeType: "application/json",
          temperature: 0.2,
        }
      });

      const responseText = response.text || "";
      const result = JSON.parse(responseText.trim());
      newContent = result.newContent || oldContent;
      explanation = result.explanation || "File berhasil disesuaikan secara otomatis.";
    } catch (e: any) {
      console.error("Failed to call Gemini for file editing:", e);
      explanation = "Terjadi kegagalan memproses dengan API. Kami menggunakan parser skrip lokal bawaan untuk menyesuaikan.";
    }
  }

  if (!newContent) return res.status(422).json(errorRes("AI tidak menghasilkan perubahan file; konten asli dipertahankan."));

  localConfigFiles[fileIndex].content = newContent;

  // Record this action internally
  const logPrompt = `[Auto-Edit] User requested auto-edit on ${fileName} with prompt: "${instruction}"`;
  const logResponse = `[Auto-Edit Sukses] ${explanation}\n\nIsi file ${fileName} berhasil dimodifikasi secara dinamis.`;
  
  aiConversations.unshift({
    id: aiConversations.length + 1,
    timestamp: new Date().toISOString(),
    user_prompt: logPrompt,
    ai_response: logResponse,
    category: "Auto-Edit",
    curation_status: "Curated"
  });

  return res.json(successRes({
    fileName,
    oldContent,
    newContent,
    explanation
  }));
});

// ==========================================
// BACKGROUND AUTOMATED BILLING SCHEDULER ENGINE
// ==========================================
function runAutomatedBillingEngine(manualTrigger = false) {
  if (!billingSettings.auto_billing_enabled && !manualTrigger) {
    return { status: "suspended", reason: "Automated billing is toggled off" };
  }

  const now = new Date();
  let generatedCount = 0;
  let overdueCount = 0;
  let isolatedCount = 0;
  const logsProduced: string[] = [];

  // 1. Recurring Auto Generation
  // Check active customers and auto generate next monthly invoice if no unpaid invoices exist
  customers.forEach(cust => {
    if (cust.status === "active") {
      const unpaidInvoices = invoices.filter(i => i.customer_id === cust.id && i.status !== "paid");
      
      // If customer has no outstanding bills, issue modern recurring monthly invoice
      if (unpaidInvoices.length === 0) {
        const packageObj = packages.find(p => p.id === cust.package_id);
        const invId = invoices.length + 1;
        
        const monthYearStr = `${now.getFullYear()}${String(now.getMonth() + 1).padStart(2, '0')}`;
        const invNum = `INV/${monthYearStr}/${String(invId).padStart(4, '0')}`;
        const amount = packageObj ? packageObj.price : 150000;
        
        const dueDays = billingSettings.due_days || 10;
        const dueDate = new Date(Date.now() + dueDays * 24 * 60 * 60 * 1000);
        
        const newInv = {
          id: invId,
          tenant_id: 1,
          customer_id: cust.id,
          invoice_number: invNum,
          amount,
          due_date: dueDate.toISOString(),
          status: "unpaid" as const,
          generated_at: now.toISOString(),
          created_at: now.toISOString(),
          updated_at: now.toISOString()
        };
        
        invoices.push(newInv);
        generatedCount++;

        // Render template message
        let textMsg = billingSettings.template_invoice_created
          .replace(/{customer_name}/g, cust.full_name)
          .replace(/{invoice_number}/g, invNum)
          .replace(/{invoice_amount}/g, "Rp " + amount.toLocaleString("id-ID"))
          .replace(/{due_date}/g, dueDate.toLocaleDateString("id-ID"))
          .replace(/{service_plan}/g, packageObj ? packageObj.name : "Internet Plan");

        // Log whatsapp notification queue
        whatsappLogs.unshift({
          id: whatsappLogs.length + 1,
          tenant_id: 1,
          customer_id: cust.id,
          phone: cust.phone || "0812345678",
          message: textMsg,
          status: "sent" as const,
          sent_at: now.toISOString()
        });

        // Add template to telegram NOC group log if telegram BOT is up
        const token = gatewaySettings.telegram_bot_token;
        if (token && token.includes(":")) {
          telegramLogs.unshift({
            id: telegramLogs.length + 1,
            tenant_id: 1,
            chat_id: gatewaySettings.telegram_chat_id || "@noc_jayanet",
            message: `[SISTEM] Invoice Baru Terbit: ${invNum} untuk ${cust.full_name} (${textMsg})`,
            status: "sent" as const,
            sent_at: now.toISOString()
          });
        }

        // Add activity log
        activityLogs.unshift({
          id: activityLogs.length + 1,
          tenant_id: 1,
          user_id: 1,
          user_name: "Sistem Otomatis",
          action: "invoice.generate",
          entity_type: "Invoice",
          entity_id: invId,
          ip_address: "127.0.0.1",
          created_at: now.toISOString()
        });

        logsProduced.push(`Auto-generated invoice ${invNum} for client ${cust.full_name}`);
      }
    }
  });

  // 2. Conversion of unpaid invoices past due to OVERDUE & suspension
  invoices.forEach(inv => {
    if (inv.status === "unpaid") {
      const dueDate = new Date(inv.due_date);
      if (now > dueDate) {
        (inv as any).status = "overdue";
        inv.updated_at = now.toISOString();
        overdueCount++;

        const cust = customers.find(c => c.id === inv.customer_id);
        if (cust) {
          const packageObj = packages.find(p => p.id === cust.package_id);
          
          let warnMsg = billingSettings.template_invoice_overdue
            .replace(/{customer_name}/g, cust.full_name)
            .replace(/{invoice_number}/g, inv.invoice_number)
            .replace(/{invoice_amount}/g, "Rp " + inv.amount.toLocaleString("id-ID"))
            .replace(/{due_date}/g, dueDate.toLocaleDateString("id-ID"))
            .replace(/{service_plan}/g, packageObj ? packageObj.name : "Internet Plan");

          whatsappLogs.unshift({
            id: whatsappLogs.length + 1,
            tenant_id: 1,
            customer_id: cust.id,
            phone: cust.phone || "0812345678",
            message: warnMsg,
            status: "sent" as const,
            sent_at: now.toISOString()
          });

          // If auto isolation is turned on, suspend customer
          if (billingSettings.auto_isolate_overdue && cust.status === "active") {
            cust.status = "suspended";
            cust.updated_at = now.toISOString();
            isolatedCount++;

            // Disable related PPPoE Accounts
            const pppAcc = pppoeAccounts.find(acc => acc.customer_id === cust.id);
            if (pppAcc) {
              pppAcc.status = "disabled";
              pppAcc.updated_at = now.toISOString();
            }

            // Log activity log
            activityLogs.unshift({
              id: activityLogs.length + 1,
              tenant_id: 1,
              user_id: 1,
              user_name: "Sistem Otomatis (Cron)",
              action: "customer.suspend",
              entity_type: "Customer",
              entity_id: cust.id,
              ip_address: "127.0.0.1",
              created_at: now.toISOString()
            });

            logsProduced.push(`Auto-isolated subscriber ${cust.full_name} because of unpaid invoice ${inv.invoice_number}`);
          }
        }
      }
    }
  });

  return {
    status: "success",
    timestamp: now.toISOString(),
    generatedCount,
    overdueCount,
    isolatedCount,
    logs: logsProduced
  };
}

// Configure Vite or Static delivery
const startServer = async () => {
  const isDev = process.env.NODE_ENV !== "production";
  const hasSource = fs.existsSync(path.join(process.cwd(), "src")) && fs.existsSync(path.join(process.cwd(), "vite.config.ts"));

  if (isDev && hasSource) {
    try {
      // Lazy load Vite with a dynamic import so that it is not required on start in compiled bundles
      const { createServer: createViteServer } = await import("vite");
      const vite = await createViteServer({
        server: { middlewareMode: true },
        appType: "spa",
      });
      app.use(vite.middlewares);
      console.log("[MikRobiL Backend] Dynamic Vite development middleware loaded.");
    } catch (viteErr) {
      console.error("[MikRobiL Backend] Dynamic Vite loading failed. Falling back to static files:", viteErr);
      const distPath = path.join(process.cwd(), "dist");
      app.use(express.static(distPath));
      app.get("*", (req, res) => {
        res.sendFile(path.join(distPath, "index.html"));
      });
    }
  } else {
    const distPath = path.join(process.cwd(), "dist");
    app.use(express.static(distPath));
    app.get("*", (req, res) => {
      res.sendFile(path.join(distPath, "index.html"));
    });
  }

  startRadiusServer({ cwd: process.cwd() });

  // Set up background periodic interval for the billing engine
  console.log("[MikRobiL AutoBilling Core] Starting interval scheduler thread...");
  setInterval(() => {
    try {
      runAutomatedBillingEngine();
    } catch (err) {
      console.error("[AutoBilling Engine Error]:", err);
    }
  }, 30000);

  if (typeof PORT === "string" && isNaN(Number(PORT))) {
    // Unix Socket support for cPanel Phusion Passenger
    app.listen(PORT, () => {
      console.log(`[MikRobiL Backend] Server booting on Unix Socket Path ${PORT} in ${process.env.NODE_ENV || 'production'} mode`);
    });
  } else {
    app.listen(Number(PORT), "0.0.0.0", () => {
      console.log(`[MikRobiL Backend] Server booting on http://localhost:${PORT} in ${process.env.NODE_ENV || 'production'} mode`);
    });
  }
};

startServer().catch(err => {
  console.error("Failed to start MikRobiL application server:", err);
});
