// acot-server — Phase 5 of CONSOLIDATION_PLAN.md.
// Standalone service on ACOT_PORT (default 3012) exposing /api/acot/* against
// the production MySQL `sg` database via an ssh2 tunnel (see db/connection.js).
//
// Auth model (two flavors, deliberate):
//   - /api/acot/customers/*  : x-acot-api-key shared secret (used by acot-phone-server).
//                              Mounted BEFORE authenticate() so its requirePhoneApiKey
//                              path is the only gate.
//   - everything else        : JWT Bearer via shared/auth/middleware.js authenticate().
//                              Defense-in-depth on top of Caddy forward_auth.
//
// Shared infrastructure (Phase 2 + Phase 6):
//   - shared/auth/middleware.js  authenticate() for SPA-served routes
//   - shared/cors/policy.js      explicit allowed-origins list (Phase 6.6)
//   - shared/logging/request-log.js  pino-http, Authorization/Cookie redacted (Phase 6.5/6.9)
//   - shared/errors/handler.js   consistent error envelope, no leak in prod
//
// Env layering: /var/www/inventory/.env loaded FIRST (JWT_SECRET, DB_* for the
// shared PG pool used by authenticate to look up user permissions). Local .env
// loaded SECOND for ACOT-specific keys (PROD_DB_*, PROD_SSH_*, ACOT_PHONE_API_KEY).
// dotenv defaults to override:false, so the first file wins on collisions.

import { config as loadEnv } from 'dotenv';
import express from 'express';
import cors from 'cors';
import compression from 'compression';
import morgan from 'morgan';
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import pg from 'pg';

import { authenticate } from '../../shared/auth/middleware.js';
import { corsOptions } from '../../shared/cors/policy.js';
import { errorHandler } from '../../shared/errors/handler.js';
import { logger } from '../../shared/logging/logger.js';
import { requestLog } from '../../shared/logging/request-log.js';

import { closeAllConnections } from './db/connection.js';

import testRouter from './routes/test.js';
import eventsRouter from './routes/events.js';
import discountsRouter from './routes/discounts.js';
import employeeMetricsRouter from './routes/employee-metrics.js';
import payrollMetricsRouter from './routes/payroll-metrics.js';
import operationsMetricsRouter from './routes/operations-metrics.js';
import customersRouter from './routes/customers.js';

const { Pool } = pg;

const __dirname = path.dirname(fileURLToPath(import.meta.url));

// Layer envs: shared inventory .env first (JWT_SECRET, DB_*) then acot .env.
const sharedEnvPath = '/var/www/inventory/.env';
const localEnvPath = path.resolve(__dirname, '.env');
if (fs.existsSync(sharedEnvPath)) loadEnv({ path: sharedEnvPath });
if (fs.existsSync(localEnvPath)) loadEnv({ path: localEnvPath });

// Phase 6.4 — refuse to start without JWT_SECRET. authenticate() would reject
// every request anyway; failing fast surfaces the misconfiguration immediately.
if (!process.env.JWT_SECRET) {
  logger.error('JWT_SECRET is not set; refusing to start (per Phase 6.4)');
  process.exit(1);
}

const app = express();
const PORT = Number(process.env.ACOT_PORT) || 3012;

// Trust X-Forwarded-* only when the immediate hop is loopback (Caddy on the same
// host). Required for the KIOSK_IPS bypass in shared/auth/middleware.js to see
// real client IPs instead of 127.0.0.1.
app.set('trust proxy', 'loopback');

// Postgres pool for authenticate() (user/permission lookups against inventory_db).
// All MySQL access goes through db/connection.js (separate, ssh-tunneled).
const pool = new Pool({
  host: process.env.DB_HOST,
  user: process.env.DB_USER,
  password: process.env.DB_PASSWORD,
  database: process.env.DB_NAME,
  port: Number(process.env.DB_PORT) || 5432,
});

// Per-app access log on disk (kept from pre-conversion behavior; pino request-log
// is mounted below for structured/redacted server-side logging).
const logDir = path.join(__dirname, 'logs/app');
if (!fs.existsSync(logDir)) {
  fs.mkdirSync(logDir, { recursive: true });
}
const accessLogStream = fs.createWriteStream(path.join(logDir, 'access.log'), { flags: 'a' });

app.use(requestLog());
app.use(compression());
app.use(cors(corsOptions));
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
if (process.env.NODE_ENV === 'production') {
  app.use(morgan('combined', { stream: accessLogStream }));
} else {
  app.use(morgan('dev'));
}

app.get('/health', (req, res) => {
  res.json({
    status: 'healthy',
    service: 'acot-server',
    timestamp: new Date().toISOString(),
    uptime: process.uptime(),
  });
});

// Customers route uses x-acot-api-key (shared secret with acot-phone-server),
// NOT JWT — mount BEFORE authenticate() so requirePhoneApiKey is the only gate.
app.use('/api/acot/customers', customersRouter);

// All remaining /api/acot/* routes require a valid JWT.
app.use('/api/acot', authenticate({ pool, secret: process.env.JWT_SECRET }));

app.use('/api/acot/test', testRouter);
app.use('/api/acot/events', eventsRouter);
app.use('/api/acot/discounts', discountsRouter);
app.use('/api/acot/employee-metrics', employeeMetricsRouter);
app.use('/api/acot/payroll-metrics', payrollMetricsRouter);
app.use('/api/acot/operations-metrics', operationsMetricsRouter);

// 404 for unmatched /api routes (keeps prior behavior).
app.use((req, res) => {
  res.status(404).json({ success: false, error: 'Route not found' });
});

app.use(errorHandler);

const server = app.listen(PORT, '0.0.0.0', () => {
  logger.info({ port: PORT, mode: process.env.NODE_ENV || 'development' }, 'acot-server listening');
});

const gracefulShutdown = async (signal) => {
  logger.info({ signal }, 'acot-server shutting down');
  server.close(async () => {
    try {
      await closeAllConnections();
    } catch (err) {
      logger.error({ err: { message: err.message } }, 'error closing MySQL pool');
    }
    try {
      await pool.end();
    } catch { /* ignore */ }
    process.exit(0);
  });
};

process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
process.on('SIGINT', () => gracefulShutdown('SIGINT'));

process.on('uncaughtException', (err) => {
  logger.error({ err: { message: err.message, stack: err.stack } }, 'uncaughtException');
  process.exit(1);
});
process.on('unhandledRejection', (reason) => {
  logger.error({ reason }, 'unhandledRejection');
});

export default app;
