import { Router, Request, Response } from 'express';
import { asyncHandler } from '../utils/asyncHandler';
import { db } from '../db';
import { users } from '../db/schema';
import { eq } from 'drizzle-orm';
import { generateToken, generateId, getCurrentTimestamp } from '../utils/auth';
import { AppError } from '../middleware/errorHandler';
import { z } from 'zod';
import bcrypt from 'bcryptjs';
import rateLimit from 'express-rate-limit';
import crypto from 'crypto';

const router = Router();

// Rate limiting for auth endpoints
const authLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 10, // limit each IP to 10 requests per windowMs
  message: {
    error: {
      code: 'RATE_LIMITED',
      message: 'Too many attempts. Please try again later.',
    },
  },
  standardHeaders: true,
  legacyHeaders: false,
});

const loginLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 5, // stricter limit for login
  message: {
    error: {
      code: 'RATE_LIMITED',
      message: 'Too many login attempts. Please try again later.',
    },
  },
  standardHeaders: true,
  legacyHeaders: false,
});

const passwordResetLimiter = rateLimit({
  windowMs: 60 * 60 * 1000, // 1 hour
  max: 3, // very strict for password reset
  message: {
    error: {
      code: 'RATE_LIMITED',
      message: 'Too many password reset requests. Please try again later.',
    },
  },
  standardHeaders: true,
  legacyHeaders: false,
});

const usernameSchema = z
  .string()
  .min(3)
  .max(30)
  .regex(/^[a-zA-Z0-9_.-]+$/, 'Username can only contain letters, numbers, dots, dashes and underscores');

const registerSchema = z.object({
  username: usernameSchema,
  password: z.string().min(8),
});

const loginSchema = z.object({
  username: z.string(),
  password: z.string(),
});

const forgotPasswordSchema = z.object({
  username: z.string().min(1),
});

const resetPasswordSchema = z.object({
  token: z.string().min(1),
  password: z.string().min(8),
});

const BCRYPT_ROUNDS = 12;

async function hashPassword(password: string): Promise<string> {
  return bcrypt.hash(password, BCRYPT_ROUNDS);
}

async function verifyPassword(password: string, hash: string): Promise<boolean> {
  return bcrypt.compare(password, hash);
}

function generateResetToken(): string {
  return crypto.randomBytes(32).toString('hex');
}

router.post('/register', authLimiter, asyncHandler(async (req: Request, res: Response) => {
  const data = registerSchema.parse(req.body);

  const existing = await db.select().from(users).where(eq(users.username, data.username)).limit(1);
  if (existing.length > 0) {
    throw new AppError('USERNAME_EXISTS', 'Username already registered', 409);
  }

  const userId = generateId('user');
  const now = getCurrentTimestamp();

  await db.insert(users).values({
    id: userId,
    username: data.username,
    passwordHash: await hashPassword(data.password),
    createdAt: now,
  });

  const token = generateToken({ userId, username: data.username });

  res.status(201).json({
    user: { id: userId, username: data.username },
    token,
  });
}));

router.post('/login', loginLimiter, asyncHandler(async (req: Request, res: Response) => {
  const data = loginSchema.parse(req.body);

  const user = await db.select().from(users).where(eq(users.username, data.username)).limit(1);
  if (user.length === 0 || !verifyPassword(data.password, user[0].passwordHash)) {
    throw new AppError('INVALID_CREDENTIALS', 'Invalid username or password', 401);
  }

  const token = generateToken({ userId: user[0].id, username: user[0].username });

  res.json({
    user: { id: user[0].id, username: user[0].username },
    token,
  });
}));

router.get('/me', asyncHandler(async (req: Request, res: Response) => {
  if (!req.user) {
    throw new AppError('UNAUTHORIZED', 'Authentication required', 401);
  }

  const user = await db.select().from(users).where(eq(users.id, req.user.userId)).limit(1);
  if (user.length === 0) {
    throw new AppError('NOT_FOUND', 'User not found', 404);
  }

  res.json({ id: user[0].id, username: user[0].username });
}));

router.post('/forgot-password', passwordResetLimiter, asyncHandler(async (req: Request, res: Response) => {
  const data = forgotPasswordSchema.parse(req.body);

  const user = await db.select().from(users).where(eq(users.username, data.username)).limit(1);
  // Always return success to prevent username enumeration
  if (user.length === 0) {
    res.json({ success: true });
    return;
  }

  const resetToken = generateResetToken();
  const resetTokenExpiry = Date.now() + 3600000; // 1 hour

  await db
    .update(users)
    .set({ resetToken, resetTokenExpiry })
    .where(eq(users.id, user[0].id));

  // In production, send email with reset link
  // For now, return token in response (dev only)
  if (process.env.NODE_ENV !== 'production') {
    res.json({ success: true, resetToken });
  } else {
    res.json({ success: true });
  }
}));

router.post('/reset-password', passwordResetLimiter, asyncHandler(async (req: Request, res: Response) => {
  const data = resetPasswordSchema.parse(req.body);

  const user = await db
    .select()
    .from(users)
    .where(eq(users.resetToken, data.token))
    .limit(1);

  if (user.length === 0 || !user[0].resetTokenExpiry || user[0].resetTokenExpiry < Date.now()) {
    throw new AppError('INVALID_TOKEN', 'Invalid or expired reset token', 400);
  }

  const newPasswordHash = await hashPassword(data.password);

  await db
    .update(users)
    .set({ passwordHash: newPasswordHash, resetToken: null, resetTokenExpiry: null })
    .where(eq(users.id, user[0].id));

  res.json({ success: true });
}));

export default router;
