import { Router } from 'express';
import bcrypt from 'bcrypt';
import { prisma } from '../db.js';
import { requireAdmin, publicUser } from '../middleware.js';
import { isValidRarity, RARITIES } from '../rarities.js';
import { createUploader, processUploadedImage } from '../upload.js';
import { onlineUserCount, totalSocketCount } from '../presence.js';
import { ACHIEVEMENT_DEFS } from '../achievements.js';
import {
  ITEM_CATEGORIES,
  normalizeCategory,
  ensureCategory,
  listCategoriesWithCounts,
} from '../categories.js';
import { toCentsBigInt, centsJson } from '../cents.js';
import { toPrBigInt, prJson } from '../prestige.js';
import {
  listShopOsuPacksAdmin,
  updateShopOsuPack,
  setOsuTopupsSectionEnabled,
} from '../shopOsu.js';

const router = Router();
const upload = createUploader('asset');

/** Parse admin money input (cents) → non-negative decimal string for Prisma. */
function parseCentsField(raw) {
  const bi = toCentsBigInt(raw);
  if (bi < 0n) return null;
  return centsJson(bi);
}

router.post('/login', async (req, res) => {
  try {
    const username = String(req.body.username || '').trim();
    const password = String(req.body.password || '');

    const user = await prisma.user.findUnique({ where: { username } });
    if (!user || user.role !== 'admin') {
      return res.status(401).json({ error: 'Invalid admin credentials' });
    }

    const ok = await bcrypt.compare(password, user.passwordHash);
    if (!ok) {
      return res.status(401).json({ error: 'Invalid admin credentials' });
    }

    req.session.userId = user.id;
    req.session.role = user.role;

    res.json({ user: publicUser(user) });
  } catch (err) {
    console.error(err);
    res.status(500).json({ error: 'Admin login failed' });
  }
});

router.get('/rarities', requireAdmin, (_req, res) => {
  res.json({ rarities: RARITIES });
});

router.get('/shop/osu-packs', requireAdmin, async (_req, res) => {
  try {
    const data = await listShopOsuPacksAdmin();
    res.json(data);
  } catch (err) {
    console.error(err);
    res.status(500).json({ error: 'Failed to load osu packs' });
  }
});

router.put('/shop/osu-section', requireAdmin, async (req, res) => {
  try {
    const enabled = Boolean(req.body.enabled);
    const sectionEnabled = await setOsuTopupsSectionEnabled(enabled);
    const data = await listShopOsuPacksAdmin();
    res.json({ ...data, sectionEnabled });
  } catch (err) {
    console.error(err);
    res.status(500).json({ error: 'Failed to update shop section' });
  }
});

router.put('/shop/osu-packs/:id', requireAdmin, async (req, res) => {
  try {
    const pack = await updateShopOsuPack(req.params.id, req.body || {});
    res.json({ pack });
  } catch (err) {
    const status = err.status || 500;
    if (status >= 500) console.error(err);
    res.status(status).json({ error: err.message || 'Failed to update pack' });
  }
});

router.get('/categories', requireAdmin, async (_req, res) => {
  try {
    const categories = await listCategoriesWithCounts();
    res.json({
      categories,
      names: categories.map((c) => c.name),
      suggested: ITEM_CATEGORIES,
    });
  } catch (err) {
    console.error(err);
    res.status(500).json({ error: 'Failed to load categories' });
  }
});

router.post('/categories', requireAdmin, async (req, res) => {
  try {
    const name = normalizeCategory(req.body.name);
    const existing = await prisma.category.findUnique({ where: { name } });
    if (existing) {
      return res.status(400).json({ error: 'Category already exists' });
    }
    const category = await prisma.category.create({ data: { name } });
    res.status(201).json({
      category: { ...category, itemCount: 0 },
    });
  } catch (err) {
    console.error(err);
    res.status(500).json({ error: 'Failed to create category' });
  }
});

router.put('/categories/:id', requireAdmin, async (req, res) => {
  try {
    const id = Number(req.params.id);
    const name = normalizeCategory(req.body.name);
    const current = await prisma.category.findUnique({ where: { id } });
    if (!current) {
      return res.status(404).json({ error: 'Category not found' });
    }
    if (name !== current.name) {
      const clash = await prisma.category.findUnique({ where: { name } });
      if (clash) {
        return res.status(400).json({ error: 'Category name already taken' });
      }
    }

    const updated = await prisma.$transaction(async (tx) => {
      const category = await tx.category.update({
        where: { id },
        data: { name },
      });
      if (name !== current.name) {
        await tx.item.updateMany({
          where: { category: current.name },
          data: { category: name },
        });
      }
      const itemCount = await tx.item.count({ where: { category: name } });
      return { ...category, itemCount };
    });

    res.json({ category: updated });
  } catch (err) {
    console.error(err);
    res.status(500).json({ error: 'Failed to update category' });
  }
});

router.delete('/categories/:id', requireAdmin, async (req, res) => {
  try {
    const id = Number(req.params.id);
    const current = await prisma.category.findUnique({ where: { id } });
    if (!current) {
      return res.status(404).json({ error: 'Category not found' });
    }
    const itemCount = await prisma.item.count({ where: { category: current.name } });
    if (itemCount > 0) {
      return res.status(400).json({
        error: `Cannot delete: ${itemCount} item${itemCount === 1 ? '' : 's'} still use this category`,
      });
    }
    await prisma.category.delete({ where: { id } });
    res.json({ ok: true });
  } catch (err) {
    console.error(err);
    res.status(500).json({ error: 'Failed to delete category' });
  }
});

router.get('/stats', requireAdmin, async (_req, res) => {
  try {
    const [
      players,
      casesTotal,
      casesActive,
      items,
      inventoryItems,
      caseOpens,
      achievementsUnlocked,
      recentUsers,
    ] = await Promise.all([
      prisma.user.count({ where: { role: 'user' } }),
      prisma.case.count(),
      prisma.case.count({ where: { active: true } }),
      prisma.item.count(),
      prisma.inventoryItem.count(),
      prisma.transaction.count({ where: { type: 'open_case' } }),
      prisma.userAchievement.count(),
      prisma.user.findMany({
        where: { role: 'user' },
        orderBy: { lastConnection: 'desc' },
        take: 5,
        select: {
          id: true,
          username: true,
          avatarUrl: true,
          balance: true,
          lastConnection: true,
        },
      }),
    ]);

    res.json({
      stats: {
        players,
        casesTotal,
        casesActive,
        items,
        inventoryItems,
        caseOpens,
        achievementsDefined: ACHIEVEMENT_DEFS.length,
        achievementsUnlocked,
        onlinePlayers: onlineUserCount(),
        openWindows: totalSocketCount(),
      },
      recentPlayers: recentUsers.map((u) => ({
        id: u.id,
        username: u.username,
        avatarUrl: u.avatarUrl || '',
        balance: u.balance,
        lastConnection: u.lastConnection,
      })),
    });
  } catch (err) {
    console.error(err);
    res.status(500).json({ error: 'Failed to load admin stats' });
  }
});

router.get('/users', requireAdmin, async (_req, res) => {
  try {
    const users = await prisma.user.findMany({
      where: { role: 'user' },
      include: {
        inventory: { select: { valueCents: true } },
        _count: {
          select: {
            achievements: true,
          },
        },
      },
      orderBy: { id: 'asc' },
    });

    const openCounts = await prisma.transaction.groupBy({
      by: ['userId'],
      where: { type: 'open_case' },
      _count: { _all: true },
    });
    const opensByUser = new Map(openCounts.map((r) => [r.userId, r._count._all]));

    res.json({
      users: users.map((u) => {
        const inventoryValue = u.inventory.reduce(
          (s, inv) => s + toCentsBigInt(inv.valueCents),
          0n
        );
        const balance = toCentsBigInt(u.balance);
        return {
          id: u.id,
          username: u.username,
          avatarUrl: u.avatarUrl || '',
          balance: centsJson(balance),
          osuBalance: u.osuBalance ?? 0,
          prBalance: prJson(u.prBalance ?? 0),
          inventoryValue: centsJson(inventoryValue),
          inventoryCount: u.inventory.length,
          totalWealth: centsJson(balance + inventoryValue),
          casesOpened: opensByUser.get(u.id) || 0,
          achievements: u._count.achievements,
          prestigeCount: u.prestigeCount ?? 0,
          lastConnection: u.lastConnection,
          playTimeSeconds: Number(u.playTimeSeconds ?? 0),
          createdAt: u.createdAt,
        };
      }),
    });
  } catch (err) {
    console.error(err);
    res.status(500).json({ error: 'Failed to load users' });
  }
});

router.patch('/users/:id', requireAdmin, async (req, res) => {
  try {
    const id = Number(req.params.id);
    const user = await prisma.user.findUnique({ where: { id } });
    if (!user || user.role !== 'user') {
      return res.status(404).json({ error: 'Player not found' });
    }

    const data = {};
    if (req.body.balance !== undefined) {
      const balance = String(req.body.balance).trim();
      if (!/^\d+$/.test(balance)) {
        return res.status(400).json({ error: 'balance must be a non-negative integer (cents)' });
      }
      data.balance = balance;
    }
    if (req.body.balanceDelta !== undefined) {
      const deltaRaw = String(req.body.balanceDelta).trim();
      if (!/^-?\d+$/.test(deltaRaw)) {
        return res.status(400).json({ error: 'balanceDelta must be an integer (cents)' });
      }
      const next = toCentsBigInt(user.balance) + BigInt(deltaRaw);
      data.balance = (next < 0n ? 0n : next).toString();
    }
    if (req.body.osuBalance !== undefined) {
      const osu = Number(req.body.osuBalance);
      if (!Number.isInteger(osu) || osu < 0) {
        return res.status(400).json({ error: 'osuBalance must be a non-negative integer' });
      }
      data.osuBalance = osu;
    }
    if (req.body.osuBalanceDelta !== undefined) {
      const delta = Number(req.body.osuBalanceDelta);
      if (!Number.isInteger(delta) || delta === 0) {
        return res.status(400).json({ error: 'osuBalanceDelta must be a non-zero integer' });
      }
      data.osuBalance = Math.max(0, (user.osuBalance ?? 0) + delta);
    }
    if (req.body.prBalanceDelta !== undefined) {
      const deltaRaw = String(req.body.prBalanceDelta).trim();
      if (!/^-?\d+$/.test(deltaRaw) || deltaRaw === '0' || deltaRaw === '-0') {
        return res.status(400).json({ error: 'prBalanceDelta must be a non-zero integer' });
      }
      const next = toPrBigInt(user.prBalance) + BigInt(deltaRaw);
      data.prBalance = prJson(next < 0n ? 0n : next);
    }

    if (!Object.keys(data).length) {
      return res.status(400).json({ error: 'No changes provided' });
    }

    const updated = await prisma.$transaction(async (tx) => {
      const next = await tx.user.update({ where: { id }, data });
      if (
        data.balance !== undefined &&
        toCentsBigInt(data.balance) !== toCentsBigInt(user.balance)
      ) {
        await tx.transaction.create({
          data: {
            userId: id,
            type: 'admin_adjust',
            amount: centsJson(toCentsBigInt(data.balance) - toCentsBigInt(user.balance)),
            meta: JSON.stringify({ reason: 'admin_panel', by: req.session.userId }),
          },
        });
      }
      return next;
    });

    res.json({ user: publicUser(updated) });
  } catch (err) {
    console.error(err);
    res.status(500).json({ error: 'Failed to update user' });
  }
});

router.post('/upload', requireAdmin, (req, res) => {
  upload.single('image')(req, res, async (err) => {
    if (err) {
      return res.status(400).json({ error: err.message || 'Upload failed' });
    }
    if (!req.file) {
      return res.status(400).json({ error: 'No file uploaded' });
    }
    try {
      await processUploadedImage(req.file);
      res.status(201).json({ url: `/uploads/${req.file.filename}` });
    } catch (e) {
      console.error(e);
      res.status(400).json({ error: 'Image processing failed' });
    }
  });
});

// —— Cases ——
router.get('/cases', requireAdmin, async (_req, res) => {
  try {
    const cases = await prisma.case.findMany({
      include: {
        items: { include: { item: true } },
      },
      orderBy: { id: 'asc' },
    });
    res.json({
      cases: cases.map((c) => ({
        id: c.id,
        name: c.name,
        imageUrl: c.imageUrl,
        price: c.price,
        active: c.active,
        items: c.items.map((ci) => ({
          id: ci.id,
          dropRate: ci.dropRate,
          item: {
            id: ci.item.id,
            name: ci.item.name,
            rarity: ci.item.rarity,
            category: ci.item.category || 'Misc',
            marketValue: ci.item.marketValue,
            imageUrl: ci.item.imageUrl,
          },
        })),
      })),
    });
  } catch (err) {
    console.error(err);
    res.status(500).json({ error: 'Failed to load cases' });
  }
});

router.post('/cases', requireAdmin, async (req, res) => {
  try {
    const name = String(req.body.name || '').trim();
    const imageUrl = String(req.body.imageUrl || '');
    const price = parseCentsField(req.body.price);
    const active = req.body.active !== false;

    if (!name) return res.status(400).json({ error: 'Name is required' });
    if (price == null) {
      return res.status(400).json({ error: 'Price must be a non-negative integer (cents)' });
    }

    const created = await prisma.case.create({
      data: { name, imageUrl, price, active },
    });
    res.status(201).json({ case: created });
  } catch (err) {
    console.error(err);
    res.status(500).json({ error: 'Failed to create case' });
  }
});

router.put('/cases/:id', requireAdmin, async (req, res) => {
  try {
    const id = Number(req.params.id);
    const data = {};
    if (req.body.name !== undefined) data.name = String(req.body.name).trim();
    if (req.body.imageUrl !== undefined) data.imageUrl = String(req.body.imageUrl);
    if (req.body.price !== undefined) {
      const price = parseCentsField(req.body.price);
      if (price == null) {
        return res.status(400).json({ error: 'Invalid price' });
      }
      data.price = price;
    }
    if (req.body.active !== undefined) data.active = Boolean(req.body.active);

    const updated = await prisma.case.update({ where: { id }, data });
    res.json({ case: updated });
  } catch (err) {
    console.error(err);
    res.status(500).json({ error: 'Failed to update case' });
  }
});

router.delete('/cases/:id', requireAdmin, async (req, res) => {
  try {
    const id = Number(req.params.id);
    await prisma.case.delete({ where: { id } });
    res.json({ ok: true });
  } catch (err) {
    console.error(err);
    res.status(500).json({ error: 'Failed to delete case' });
  }
});

// —— Items ——
router.get('/items', requireAdmin, async (_req, res) => {
  try {
    const items = await prisma.item.findMany({ orderBy: [{ category: 'asc' }, { id: 'asc' }] });
    res.json({ items });
  } catch (err) {
    console.error(err);
    res.status(500).json({ error: 'Failed to load items' });
  }
});

router.post('/items', requireAdmin, async (req, res) => {
  try {
    const name = String(req.body.name || '').trim();
    const imageUrl = String(req.body.imageUrl || '');
    const rarity = String(req.body.rarity || '');
    const category = await ensureCategory(req.body.category);
    const marketValue = parseCentsField(req.body.marketValue);

    if (!name) return res.status(400).json({ error: 'Name is required' });
    if (!isValidRarity(rarity)) {
      return res.status(400).json({ error: `Invalid rarity. Use: ${RARITIES.join(', ')}` });
    }
    if (marketValue == null) {
      return res.status(400).json({ error: 'marketValue must be a non-negative integer (cents)' });
    }

    const created = await prisma.item.create({
      data: { name, imageUrl, rarity, category, marketValue },
    });
    res.status(201).json({ item: created });
  } catch (err) {
    console.error(err);
    res.status(500).json({ error: 'Failed to create item' });
  }
});

router.put('/items/:id', requireAdmin, async (req, res) => {
  try {
    const id = Number(req.params.id);
    const data = {};
    if (req.body.name !== undefined) data.name = String(req.body.name).trim();
    if (req.body.imageUrl !== undefined) data.imageUrl = String(req.body.imageUrl);
    if (req.body.rarity !== undefined) {
      if (!isValidRarity(req.body.rarity)) {
        return res.status(400).json({ error: 'Invalid rarity' });
      }
      data.rarity = req.body.rarity;
    }
    if (req.body.category !== undefined) {
      data.category = await ensureCategory(req.body.category);
    }
    if (req.body.marketValue !== undefined) {
      const marketValue = parseCentsField(req.body.marketValue);
      if (marketValue == null) {
        return res.status(400).json({ error: 'Invalid marketValue' });
      }
      data.marketValue = marketValue;
    }

    const updated = await prisma.item.update({ where: { id }, data });
    res.json({ item: updated });
  } catch (err) {
    console.error(err);
    res.status(500).json({ error: 'Failed to update item' });
  }
});

router.delete('/items/:id', requireAdmin, async (req, res) => {
  try {
    const id = Number(req.params.id);
    await prisma.item.delete({ where: { id } });
    res.json({ ok: true });
  } catch (err) {
    console.error(err);
    res.status(500).json({ error: 'Failed to delete item' });
  }
});

// —— Case drops ——
router.post('/cases/:caseId/items', requireAdmin, async (req, res) => {
  try {
    const caseId = Number(req.params.caseId);
    const itemId = Number(req.body.itemId);
    const dropRate = Number(req.body.dropRate);

    if (!Number.isInteger(itemId)) {
      return res.status(400).json({ error: 'itemId required' });
    }
    if (!(dropRate > 0)) {
      return res.status(400).json({ error: 'dropRate must be > 0' });
    }

    const created = await prisma.caseItem.create({
      data: { caseId, itemId, dropRate },
      include: { item: true },
    });
    res.status(201).json({ caseItem: created });
  } catch (err) {
    console.error(err);
    if (err.code === 'P2002') {
      return res.status(409).json({ error: 'Item already in this case' });
    }
    res.status(500).json({ error: 'Failed to add case item' });
  }
});

router.put('/case-items/:id', requireAdmin, async (req, res) => {
  try {
    const id = Number(req.params.id);
    const dropRate = Number(req.body.dropRate);
    if (!(dropRate > 0)) {
      return res.status(400).json({ error: 'dropRate must be > 0' });
    }
    const updated = await prisma.caseItem.update({
      where: { id },
      data: { dropRate },
      include: { item: true },
    });
    res.json({ caseItem: updated });
  } catch (err) {
    console.error(err);
    res.status(500).json({ error: 'Failed to update drop rate' });
  }
});

router.delete('/case-items/:id', requireAdmin, async (req, res) => {
  try {
    const id = Number(req.params.id);
    await prisma.caseItem.delete({ where: { id } });
    res.json({ ok: true });
  } catch (err) {
    console.error(err);
    res.status(500).json({ error: 'Failed to remove case item' });
  }
});

/**
 * Preview counts before a server prestige wipe.
 */
router.get('/prestige-server', requireAdmin, async (_req, res) => {
  try {
    const [players, inventory, prestigeLogs, opensKept] = await Promise.all([
      prisma.user.count({ where: { role: { not: 'admin' } } }),
      prisma.inventoryItem.count({
        where: { user: { role: { not: 'admin' } } },
      }),
      prisma.prestigeLog.count({
        where: { user: { role: { not: 'admin' } } },
      }),
      prisma.transaction.count({
        where: {
          user: { role: { not: 'admin' } },
          type: { in: ['open_case', 'jackpot_win'] },
        },
      }),
    ]);
    res.json({
      preview: {
        players,
        inventoryItems: inventory,
        prestigeLogs,
        statsTransactionsKept: opensKept,
      },
    });
  } catch (err) {
    console.error(err);
    res.status(500).json({ error: 'Failed to load prestige-server preview' });
  }
});

/**
 * Full player wipe (server prestige). Requires admin password confirmation.
 * Achievement fields must be configured before activation.
 */
router.post('/prestige-server', requireAdmin, async (req, res) => {
  try {
    const password = String(req.body.password || '');
    if (!password) {
      return res.status(400).json({ error: 'Admin password required' });
    }

    const admin = await prisma.user.findUnique({ where: { id: req.session.userId } });
    if (!admin || admin.role !== 'admin') {
      return res.status(401).json({ error: 'Unauthorized' });
    }

    const ok = await bcrypt.compare(password, admin.passwordHash);
    if (!ok) {
      return res.status(401).json({ error: 'Invalid admin password' });
    }

    const ach = req.body.achievement || {};
    const { runServerPrestige } = await import('../serverPrestige.js');
    const result = await runServerPrestige({
      achievementKey: ach.key,
      achievementName: ach.name,
      achievementDescription: ach.description,
      achievementIcon: ach.icon,
    });

    res.json({
      ok: true,
      ...result,
    });
  } catch (err) {
    console.error(err);
    const status = err.status || 500;
    res.status(status).json({ error: err.message || 'Server prestige failed' });
  }
});

export default router;
