// @WHO:   vps-deploy/api/kds/src/index.ts
// @WHAT:  KDS API Server entry point — Express + Prisma + PostgreSQL + Socket.IO
// @PART:  server
// @ENTRY: main()

import express from 'express';
import cors from 'cors';
import { createServer } from 'http';
import { Server } from 'socket.io';
import jwt from 'jsonwebtoken';
import { publicRouter } from './routes/public';
import { authRouter } from './routes/auth';
import { v1Router } from './routes/v1';
import { webhookRouter } from './routes/webhooks';
import { stockRouter } from './routes/stock';
import { recipeRouter } from './routes/recipe';
import { purchasingRouter } from './routes/purchasing';
import { transformationTaskRouter } from './routes/transformation-task';
import { errorHandler } from './middleware/error-handler';
import { prisma } from './lib/prisma';
import { setIO } from './lib/socket';

const app = express();
const PORT = parseInt(process.env.PORT || '3001', 10);

// Middleware
app.use(cors({
  origin: ['https://fecttral.com', 'http://localhost:3000', 'http://localhost:3002', 'http://localhost:3003'],
  methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
  allowedHeaders: ['Content-Type', 'Authorization'],
}));
app.use(express.json());

// Health check
app.get('/health', async (_req, res) => {
  let dbStatus = 'disconnected';
  try {
    await prisma.$queryRaw`SELECT 1`;
    dbStatus = 'connected';
  } catch {
    dbStatus = 'error';
  }

  res.json({
    status: 'ok',
    service: 'kds-api',
    version: '0.2.0',
    database: dbStatus,
    uptime: process.uptime(),
  });
});

// Routes
app.use('/api/public', publicRouter);
app.use('/api/v1/auth', authRouter);
app.use('/api/v1', v1Router);
app.use('/api/v1', stockRouter);      // Stock + ingredient management
app.use('/api/v1', recipeRouter);     // Recipe CRUD (menu ↔ ingredient)
app.use('/api/v1', purchasingRouter); // Supplier + PurchaseOrder CRUD
app.use('/api/v1', transformationTaskRouter); // Stock transformation tasks
app.use('/api/webhooks', webhookRouter);

// QRIS Payment Bridge — creates QRIS invoice via WebQRIS PHP gateway
// This endpoint bridges the DapurKu API to the existing WebQRIS gateway
// @FLOW:     PAYMENT_QRIS_CREATE
// @CALLS:    DB:payments SELECT, HTTP POST to /api/payment/extension
// @MUTATES:  DB:payments (UPDATE qrInvoiceId, qrPayload)
app.post('/api/payments/qris/create', async (req, res) => {
  try {
    const { orderId } = req.body;

    if (!orderId) {
      return res.status(400).json({ error: 'orderId is required' });
    }

    const payment = await prisma.payment.findFirst({
      where: { orderId, method: 'QRIS' },
    });

    if (!payment) {
      return res.status(404).json({ error: 'Payment not found' });
    }

    if (payment.status === 'paid') {
      return res.status(400).json({ error: 'Payment already completed' });
    }

    // If we already have a QRIS invoice, return it
    if (payment.qrInvoiceId && payment.qrPayload) {
      return res.json({
        invoiceId: payment.qrInvoiceId,
        qrPayload: payment.qrPayload,
        amount: payment.amount,
        status: payment.status,
      });
    }

    // Create QRIS invoice via WebQRIS PHP gateway
    const webqrisUrl = `${process.env.WEBQRIS_BASE_URL || 'https://fecttral.com'}/api/payment/extension`;
    const webqrisResponse = await fetch(webqrisUrl, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        type: 'kedai-ayam-order',
        amount: payment.amount,
        merchant_order_id: orderId,
      }),
    });

    if (!webqrisResponse.ok) {
      console.error('[QRIS_CREATE] WebQRIS error:', webqrisResponse.status);
      return res.status(502).json({ error: 'Failed to create QRIS invoice' });
    }

    const webqrisData = await webqrisResponse.json();

    // Update payment with QRIS data
    const updated = await prisma.payment.update({
      where: { id: payment.id },
      data: {
        qrInvoiceId: webqrisData.invoice_id,
        qrPayload: webqrisData.qr_payload,
      },
    });

    res.json({
      invoiceId: webqrisData.invoice_id,
      qrPayload: webqrisData.qr_payload,
      amount: payment.amount,
      status: 'pending',
    });
  } catch (err: any) {
    console.error('[QRIS_CREATE]', err);
    res.status(500).json({ error: 'Failed to create QRIS payment', message: err.message });
  }
});

// QRIS Payment Status — poll payment status
// @FLOW:     PAYMENT_STATUS_POLL
// @CALLS:    DB:payments SELECT
// @MUTATES:  none
app.get('/api/payments/qris/status/:invoiceId', async (req, res) => {
  try {
    const payment = await prisma.payment.findFirst({
      where: { qrInvoiceId: req.params.invoiceId },
      select: {
        id: true,
        status: true,
        amount: true,
        qrInvoiceId: true,
        paidAt: true,
        order: {
          select: { id: true, orderNumber: true, status: true },
        },
      },
    });

    if (!payment) {
      return res.status(404).json({ error: 'Payment not found' });
    }

    res.json(payment);
  } catch (err: any) {
    res.status(500).json({ error: 'Failed to fetch payment status', message: err.message });
  }
});

// Error handler
app.use(errorHandler);

// Create HTTP server from Express app
const httpServer = createServer(app);

// Socket.IO server on same process
export const io = new Server(httpServer, {
  cors: {
    origin: ['https://fecttral.com', 'http://localhost:3000', 'http://localhost:3002', 'http://localhost:3003'],
    methods: ['GET', 'POST', 'PATCH'],
  },
});

setIO(io);

// ── Socket.IO JWT Authentication Middleware ──
// @FLOW:   SOCKET_AUTH
// @CALLS:  jwt.verify
// @MUTATES: socket.data.user (sets decoded JWT payload)
//
// Clients must provide a valid JWT token via:
//   - socket.handshake.auth.token (preferred, passed via Socket.IO auth option)
//   - socket.handshake.query.token (fallback, passed via URL query param)
//
// In development mode (NODE_ENV !== 'production'), authentication is optional —
// connections without a token are allowed but socket.data.user will be undefined.
const JWT_SECRET = process.env.JWT_SECRET || 'kaw81-dev-secret';

io.use(async (socket, next) => {
  const token = socket.handshake.auth.token || socket.handshake.query.token;

  // In development, allow unauthenticated connections
  if (!token && process.env.NODE_ENV !== 'production') {
    console.log(`[Socket.IO] Dev mode: allowing unauthenticated connection ${socket.id}`);
    socket.data.user = undefined;
    return next();
  }

  if (!token) {
    return next(new Error('Authentication required'));
  }

  try {
    const decoded = jwt.verify(token as string, JWT_SECRET);
    socket.data.user = decoded;
    console.log(`[Socket.IO] Authenticated: ${socket.id} (user: ${(decoded as any).userId || 'unknown'})`);
    next();
  } catch (err) {
    console.warn(`[Socket.IO] Authentication failed for ${socket.id}: ${(err as Error).message}`);
    next(new Error('Invalid token'));
  }
});

io.on('connection', (socket) => {
  console.log(`[Socket.IO] Client connected: ${socket.id}`);

  // @FLOW:   SOCKET_SUBSCRIBE_ORDER
  // @CALLS:  socket.join
  // @MUTATES: none
  socket.on('order:subscribe', (outletId: string) => {
    socket.join(`outlet:${outletId}:orders`);
  });
  // @FLOW:   SOCKET_UNSUBSCRIBE_ORDER
  // @CALLS:  socket.leave
  // @MUTATES: none
  socket.on('order:unsubscribe', (outletId: string) => {
    socket.leave(`outlet:${outletId}:orders`);
  });
  // @FLOW:   SOCKET_SUBSCRIBE_KDS
  // @CALLS:  socket.join
  // @MUTATES: none
  socket.on('kds:subscribe', (outletId: string, stationId?: string) => {
    const room = stationId ? `outlet:${outletId}:kds:station:${stationId}` : `outlet:${outletId}:kds`;
    socket.join(room);
  });
  // @FLOW:   SOCKET_UNSUBSCRIBE_KDS
  // @CALLS:  socket.leave
  // @MUTATES: none
  socket.on('kds:unsubscribe', (outletId: string, stationId?: string) => {
    const room = stationId ? `outlet:${outletId}:kds:station:${stationId}` : `outlet:${outletId}:kds`;
    socket.leave(room);
  });
  // @FLOW:   SOCKET_SUBSCRIBE_CASHIER
  // @CALLS:  socket.join
  // @MUTATES: none
  socket.on('cashier:subscribe', (outletId: string) => {
    socket.join(`outlet:${outletId}:cashier`);
  });
  // @FLOW:   SOCKET_UNSUBSCRIBE_CASHIER
  // @CALLS:  socket.leave
  // @MUTATES: none
  socket.on('cashier:unsubscribe', (outletId: string) => {
    socket.leave(`outlet:${outletId}:cashier`);
  });
  // @FLOW:   SOCKET_SUBSCRIBE_STOCK
  // @CALLS:  socket.join
  // @MUTATES: none
  socket.on('stock:subscribe', (outletId: string) => {
    socket.join(`outlet:${outletId}:stock`);
  });
  // @FLOW:   SOCKET_UNSUBSCRIBE_STOCK
  // @CALLS:  socket.leave
  // @MUTATES: none
  socket.on('stock:unsubscribe', (outletId: string) => {
    socket.leave(`outlet:${outletId}:stock`);
  });
  // @FLOW:   SOCKET_SUBSCRIBE_TASKS
  // @CALLS:  socket.join
  // @MUTATES: none
  socket.on('tasks:subscribe', (outletId: string) => {
    socket.join(`outlet:${outletId}:tasks`);
  });
  // @FLOW:   SOCKET_UNSUBSCRIBE_TASKS
  // @CALLS:  socket.leave
  // @MUTATES: none
  socket.on('tasks:unsubscribe', (outletId: string) => {
    socket.leave(`outlet:${outletId}:tasks`);
  });
  // @FLOW:   SOCKET_SUBSCRIBE_USER
  // @CALLS:  socket.join
  // @MUTATES: none
  socket.on('user:subscribe', (userId: string) => {
    socket.join(`user:${userId}`);
  });
  // @FLOW:   SOCKET_UNSUBSCRIBE_USER
  // @CALLS:  socket.leave
  // @MUTATES: none
  socket.on('user:unsubscribe', (userId: string) => {
    socket.leave(`user:${userId}`);
  });

  socket.on('disconnect', (reason) => {
    console.log(`[Socket.IO] Client disconnected: ${socket.id} (${reason})`);
  });
});

// ── Nightly Cron: Adaptive Inventory Recalculation ──
// @FLOW:   CRON_INVENTORY_RECALC
// @CALLS:  recalculateEstimates(), recalculateBaselines(), checkAnomalies()
// @MUTATES: DB:ingredient_usage_estimates (UPSERT), DB:depletion_baselines (UPSERT)
// Runs every night at 02:00 local server time
import { recalculateEstimates, recalculateBaselines } from './lib/regression';
import { checkAnomalies } from './lib/anomaly';

function scheduleNightlyRecalc() {
  const now = new Date();
  const target = new Date(now);
  target.setHours(2, 0, 0, 0); // 02:00 today
  if (target <= now) target.setDate(target.getDate() + 1); // already past 02:00, schedule tomorrow

  const delayMs = target.getTime() - now.getTime();
  console.log(`[CRON] Nightly inventory recalc scheduled in ${Math.round(delayMs / 60000)} minutes`);

  setTimeout(async () => {
    await runNightlyRecalc();
    // Re-schedule for next day
    setInterval(runNightlyRecalc, 24 * 60 * 60 * 1000);
  }, delayMs);
}

async function runNightlyRecalc() {
  console.log('[CRON] Starting nightly inventory recalculation...');
  const start = Date.now();

  try {
    // Get all active outlets
    const outlets = await prisma.outlet.findMany({ where: { isActive: true }, select: { id: true, slug: true } });

    for (const outlet of outlets) {
      try {
        // Step 1: Recalculate regression estimates
        await recalculateEstimates(outlet.id);

        // Step 2: Recalculate depletion baselines
        await recalculateBaselines(outlet.id);

        // Step 3: Check for anomalies and emit alerts
        const alerts = await checkAnomalies(outlet.id);
        if (alerts.length > 0) {
          const io = getIO();
          if (io) {
            io.to(`outlet:${outlet.id}:stock`).emit('stock:anomaly', {
              outletId: outlet.id,
              alertCount: alerts.length,
              alerts,
            });
          }
        }

        console.log(`[CRON] Outlet ${outlet.slug}: ${alerts.length} anomaly alerts`);
      } catch (err: any) {
        console.error(`[CRON] Error for outlet ${outlet.slug}:`, err.message);
      }
    }

    const elapsed = Date.now() - start;
    console.log(`[CRON] Nightly recalculation complete in ${elapsed}ms`);
  } catch (err: any) {
    console.error('[CRON] Nightly recalculation failed:', err.message);
  }
}

scheduleNightlyRecalc();

httpServer.listen(PORT, () => {
  console.log(`🍗 KDS API Server + Socket.IO running on port ${PORT}`);
  console.log(`   Health: http://localhost:${PORT}/health`);
  console.log(`   Environment: ${process.env.NODE_ENV || 'development'}`);
});

export default app;
