import { Router, Request, Response } from 'express';
import { asyncHandler } from '../utils/asyncHandler';
import { db } from '../db';
import { users, friendships } from '../db/schema';
import { eq, and, or, ilike, ne, asc } from 'drizzle-orm';
import { generateId } from '../utils/auth';
import { authMiddleware } from '../utils/auth';
import { AppError } from '../middleware/errorHandler';
import { friendRequestSchema, searchQuerySchema } from '../utils/validation';

const router = Router();

router.use(authMiddleware);

interface FriendRow {
  id: string;
  username: string;
  requestId?: string;
  status?: string;
}

async function findUsernames(ids: string[]): Promise<Map<string, string>> {
  if (ids.length === 0) return new Map();
  const rows = await db.select().from(users).where(or(...ids.map((id) => eq(users.id, id))));
  return new Map(rows.map((u) => [u.id, u.username]));
}

router.get('/', asyncHandler(async (req: Request, res: Response) => {
  const userId = req.user!.userId;

  const outgoingRows = await db
    .select()
    .from(friendships)
    .where(and(eq(friendships.userId, userId), eq(friendships.status, 'pending')));

  const incomingRows = await db
    .select()
    .from(friendships)
    .where(and(eq(friendships.friendId, userId), eq(friendships.status, 'pending')));

  const acceptedRows = await db
    .select()
    .from(friendships)
    .where(and(
      or(eq(friendships.userId, userId), eq(friendships.friendId, userId)),
      eq(friendships.status, 'accepted')
    ));

  const friendIds = acceptedRows.map((r) => (r.userId === userId ? r.friendId : r.userId));
  const outgoingIds = outgoingRows.map((r) => r.friendId);
  const incomingIds = incomingRows.map((r) => r.userId);
  const usernames = await findUsernames([...friendIds, ...outgoingIds, ...incomingIds]);

  const friends: FriendRow[] = acceptedRows.map((r) => {
    const friendId = r.userId === userId ? r.friendId : r.userId;
    return { id: friendId, username: usernames.get(friendId) ?? '' };
  });

  const outgoing: FriendRow[] = outgoingRows.map((r) => ({
    id: r.id,
    username: usernames.get(r.friendId) ?? '',
    requestId: r.id,
    status: 'pending',
  }));

  const incoming: FriendRow[] = incomingRows.map((r) => ({
    id: r.userId,
    username: usernames.get(r.userId) ?? '',
    requestId: r.id,
    status: 'pending',
  }));

  res.json({ friends, incoming, outgoing });
}));

router.get('/search', asyncHandler(async (req: Request, res: Response) => {
  const { q } = searchQuerySchema.parse(req.query);
  const userId = req.user!.userId;

  const results = await db
    .select({ id: users.id, username: users.username })
    .from(users)
    .where(and(
      ne(users.id, userId),
      ilike(users.username, `%${q}%`)
    ))
    .orderBy(asc(users.username))
    .limit(20);

  res.json(results);
}));

router.post('/requests', asyncHandler(async (req: Request, res: Response) => {
  const { username } = friendRequestSchema.parse(req.body);
  const userId = req.user!.userId;

  if (username.toLowerCase() === (await meUsername(userId))) {
    throw new AppError('SELF_REQUEST', 'You cannot add yourself', 400);
  }

  const target = await db.select().from(users).where(eq(users.username, username)).limit(1);
  if (target.length === 0) {
    throw new AppError('USER_NOT_FOUND', 'No user with that username found', 404);
  }

  const existing = await db
    .select()
    .from(friendships)
    .where(or(
      and(eq(friendships.userId, userId), eq(friendships.friendId, target[0].id)),
      and(eq(friendships.userId, target[0].id), eq(friendships.friendId, userId))
    ))
    .limit(1);

  if (existing.length > 0) {
    throw new AppError(
      'ALREADY_FRIENDS',
      existing[0].status === 'accepted' ? 'You are already friends' : 'Friend request already pending',
      409
    );
  }

  const now = Date.now();
  await db.insert(friendships).values({
    id: generateId('friend'),
    userId,
    friendId: target[0].id,
    status: 'pending',
    createdAt: now,
    updatedAt: now,
  });

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

router.post('/requests/:id/accept', asyncHandler(async (req: Request, res: Response) => {
  const userId = req.user!.userId;
  const requestId = req.params.id;

  const row = await db.select().from(friendships).where(eq(friendships.id, requestId)).limit(1);
  if (row.length === 0) {
    throw new AppError('NOT_FOUND', 'Request not found', 404);
  }
  if (row[0].friendId !== userId) {
    throw new AppError('FORBIDDEN', 'This request was not sent to you', 403);
  }
  if (row[0].status !== 'pending') {
    throw new AppError('INVALID_STATE', 'Request is no longer pending', 409);
  }

  await db
    .update(friendships)
    .set({ status: 'accepted', updatedAt: Date.now() })
    .where(eq(friendships.id, requestId));

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

router.delete('/requests/:id', asyncHandler(async (req: Request, res: Response) => {
  const userId = req.user!.userId;
  const requestId = req.params.id;

  const row = await db.select().from(friendships).where(eq(friendships.id, requestId)).limit(1);
  if (row.length === 0) {
    throw new AppError('NOT_FOUND', 'Request not found', 404);
  }
  if (row[0].userId !== userId && row[0].friendId !== userId) {
    throw new AppError('FORBIDDEN', 'Not allowed', 403);
  }

  await db.delete(friendships).where(eq(friendships.id, requestId));
  res.json({ success: true });
}));

router.delete('/:friendId', asyncHandler(async (req: Request, res: Response) => {
  const userId = req.user!.userId;
  const friendId = req.params.friendId;

  await db
    .delete(friendships)
    .where(or(
      and(eq(friendships.userId, userId), eq(friendships.friendId, friendId)),
      and(eq(friendships.userId, friendId), eq(friendships.friendId, userId))
    ));

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

async function meUsername(userId: string): Promise<string> {
  const me = await db.select().from(users).where(eq(users.id, userId)).limit(1);
  return me.length > 0 ? me[0].username.toLowerCase() : '';
}

export default router;
