import http from 'http';
import path from 'path';
import fs from 'fs';
import { fileURLToPath } from 'url';
import express from 'express';
import cors from 'cors';
import session from 'express-session';
import { Server } from 'socket.io';

import authRoutes from './routes/auth.js';
import casesRoutes from './routes/cases.js';
import inventoryRoutes from './routes/inventory.js';
import leaderboardRoutes from './routes/leaderboard.js';
import adminRoutes from './routes/admin.js';
import jackpotRoutes from './routes/jackpot.js';
import profileRoutes from './routes/profile.js';
import shopRoutes from './routes/shop.js';
import feedRoutes from './routes/feed.js';
import catalogRoutes from './routes/catalog.js';
import prestigeRoutes from './routes/prestige.js';
import achievementsRoutes from './routes/achievements.js';
import vaultRoutes from './routes/vault.js';
import { setIO } from './realtime.js';
import { getDropFeed } from './feed.js';
import { ensureJackpotRound, listJackpotBattles, getJackpotRound } from './services/jackpot.js';
import { ensureAdminFromEnv } from './ensureAdmin.js';
import { ensureAchievements, checkAchievements } from './achievements.js';
import { ensureCategories } from './categories.js';
import { ensureShopOsuPacks } from './shopOsu.js';
import { trackConnect, trackDisconnect, settleOnlineVaultForConnected } from './presence.js';
import { paymentsProvider } from './shopCatalog.js';
import { handleStripeWebhook } from './payments.js';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const uploadsDir = path.join(__dirname, '../uploads');
if (!fs.existsSync(uploadsDir)) {
  fs.mkdirSync(uploadsDir, { recursive: true });
}

const app = express();
const server = http.createServer(app);
const PORT = Number(process.env.PORT || 3001);
const CLIENT_ORIGIN = process.env.CLIENT_ORIGIN || 'http://localhost:5888';
const TRUST_PROXY = process.env.TRUST_PROXY === 'true' || process.env.TRUST_PROXY === '1';

/** true | false | 'auto' (secure cookie only on HTTPS) */
function cookieSecureOption() {
  const v = String(process.env.COOKIE_SECURE ?? 'auto').toLowerCase();
  if (v === 'true' || v === '1') return true;
  if (v === 'false' || v === '0') return false;
  return 'auto';
}

if (TRUST_PROXY) {
  app.set('trust proxy', 1);
}

function isAllowedOrigin(origin) {
  if (!origin) return true;
  if (origin === CLIENT_ORIGIN) return true;
  const extra = (process.env.CORS_ORIGINS || '')
    .split(',')
    .map((s) => s.trim())
    .filter(Boolean);
  if (extra.includes(origin)) return true;
  if (/^http:\/\/(localhost|127\.0\.0\.1)(:\d+)?$/.test(origin)) return true;
  // Private LAN ranges (dev access from phones / other PCs)
  if (/^http:\/\/192\.168\.\d{1,3}\.\d{1,3}(:\d+)?$/.test(origin)) return true;
  if (/^http:\/\/10\.\d{1,3}\.\d{1,3}\.\d{1,3}(:\d+)?$/.test(origin)) return true;
  if (/^http:\/\/172\.(1[6-9]|2\d|3[0-1])\.\d{1,3}\.\d{1,3}(:\d+)?$/.test(origin)) {
    return true;
  }
  return false;
}

const corsOptions = {
  origin(origin, callback) {
    callback(null, isAllowedOrigin(origin));
  },
  credentials: true,
};

const sessionMiddleware = session({
  secret: process.env.SESSION_SECRET || 'dev-secret',
  resave: false,
  saveUninitialized: false,
  proxy: TRUST_PROXY,
  cookie: {
    httpOnly: true,
    sameSite: 'lax',
    secure: cookieSecureOption(),
    maxAge: 7 * 24 * 60 * 60 * 1000,
  },
});

app.use(cors(corsOptions));

// Stripe webhook needs the raw body for signature verification — must run
// before express.json() for this path only.
app.post(
  '/api/shop/osu/webhook',
  express.raw({ type: 'application/json' }),
  async (req, res) => {
    try {
      if (paymentsProvider() !== 'stripe') {
        return res.status(404).json({ error: 'Stripe webhooks not enabled' });
      }
      const signature = req.headers['stripe-signature'];
      const result = await handleStripeWebhook(req.body, signature);
      res.json(result);
    } catch (err) {
      const status = err.status || 400;
      if (status >= 500) console.error(err);
      res.status(status).json({ error: err.message || 'Webhook error' });
    }
  }
);

app.use(express.json());
app.use(sessionMiddleware);
// Serialize BigInt fields (balance / valueCents) as strings for JSON responses
app.use((_req, res, next) => {
  const originalJson = res.json.bind(res);
  res.json = (body) =>
    originalJson(
      JSON.parse(
        JSON.stringify(body, (_key, value) =>
          typeof value === 'bigint' ? value.toString() : value
        )
      )
    );
  next();
});
app.use('/uploads', express.static(uploadsDir));

app.get('/api/health', (_req, res) => {
  res.json({ ok: true });
});

app.use('/api/auth', authRoutes);
app.use('/api/cases', casesRoutes);
app.use('/api/inventory', inventoryRoutes);
app.use('/api/leaderboard', leaderboardRoutes);
app.use('/api/admin', adminRoutes);
app.use('/api/jackpot', jackpotRoutes);
app.use('/api/profile', profileRoutes);
app.use('/api/shop', shopRoutes);
app.use('/api/feed', feedRoutes);
app.use('/api/catalog', catalogRoutes);
app.use('/api/prestige', prestigeRoutes);
app.use('/api/achievements', achievementsRoutes);
app.use('/api/vault', vaultRoutes);

app.use((err, _req, res, _next) => {
  console.error(err);
  res.status(500).json({ error: 'Internal server error' });
});

const io = new Server(server, {
  cors: corsOptions,
});
setIO(io);

io.use((socket, next) => {
  sessionMiddleware(socket.request, {}, next);
});

io.on('connection', async (socket) => {
  const userId = socket.request.session?.userId || null;
  socket.data.userId = userId;

  if (userId) {
    try {
      const result = await trackConnect(userId, socket.id, io);
      if (!result.ok) {
        socket.emit('session:limit', {
          max: result.max,
          count: result.count,
          message: `Too many open windows for this account (max ${result.max}). Close another tab and reload.`,
        });
        socket.disconnect(true);
        return;
      }
      // Soft-replace of older sockets no longer emits session:limit (refresh-safe).
      socket.join(`user:${userId}`);
      socket.emit('session:ok', {
        count: result.count,
        max: result.max,
        vaultCredited: result.vaultCredited || 0,
      });
    } catch (err) {
      console.error('presence connect', err);
    }
  }

  socket.on('jackpots:subscribe', async () => {
    socket.join('jackpots');
    try {
      const battles = await listJackpotBattles();
      socket.emit('jackpots:list', { battles });
    } catch (err) {
      console.error(err);
    }
  });

  socket.on('jackpots:unsubscribe', () => {
    socket.leave('jackpots');
  });

  socket.on('jackpot:subscribe', async (roundId) => {
    const id = Number(roundId);
    if (id) {
      socket.join(`jackpot:${id}`);
      try {
        const state = await getJackpotRound(id);
        if (state) socket.emit('jackpot:state', state);
      } catch (err) {
        console.error(err);
      }
      return;
    }
    // Legacy: join global room and send list head
    socket.join('jackpot');
    try {
      const battles = await listJackpotBattles();
      socket.emit('jackpots:list', { battles });
      if (battles[0]) socket.emit('jackpot:state', battles[0]);
    } catch (err) {
      console.error(err);
    }
  });

  socket.on('jackpot:unsubscribe', (roundId) => {
    const id = Number(roundId);
    if (id) {
      socket.leave(`jackpot:${id}`);
      return;
    }
    socket.leave('jackpot');
  });

  socket.on('feed:subscribe', () => {
    socket.join('feed');
    socket.emit('feed:history', getDropFeed());
  });

  socket.on('feed:unsubscribe', () => {
    socket.leave('feed');
  });

  socket.on('disconnect', () => {
    const uid = socket.data.userId || userId;
    if (uid) {
      trackDisconnect(uid, socket.id)
        .then(() => checkAchievements(uid))
        .catch((err) => console.error(err));
    }
  });
});

// Production: serve Vite build (SPA)
const publicDir = path.join(__dirname, '../public');
if (process.env.NODE_ENV === 'production' && fs.existsSync(publicDir)) {
  app.use(express.static(publicDir));
  app.use((req, res, next) => {
    if (req.method !== 'GET' && req.method !== 'HEAD') return next();
    if (
      req.path.startsWith('/api') ||
      req.path.startsWith('/uploads') ||
      req.path.startsWith('/socket.io')
    ) {
      return next();
    }
    res.sendFile(path.join(publicDir, 'index.html'));
  });
}

async function bootstrap() {
  try {
    await ensureAdminFromEnv();
    await ensureAchievements();
    await ensureCategories();
    await ensureShopOsuPacks();
  } catch (err) {
    console.error(err.message || err);
    process.exit(1);
  }

  server.listen(PORT, async () => {
    console.log(`API listening on http://localhost:${PORT}`);
    try {
      await ensureJackpotRound();
      // Online vault dividends: check every 30s for completed ticks (5 min online)
      setInterval(() => {
        settleOnlineVaultForConnected().catch((err) =>
          console.error('vault online interval', err)
        );
      }, 30_000);
    } catch (err) {
      console.error('Failed to bootstrap game services', err);
    }
  });
}

bootstrap();
