import { and, eq } from 'drizzle-orm';

import { db } from '@/lib/db';
import { user, userProfiles, userPortRoles, roles } from '@/lib/db/schema';
import { auth } from '@/lib/auth';
import { createAuditLog } from '@/lib/audit';
import { ConflictError, NotFoundError, ValidationError } from '@/lib/errors';
import { emitToRoom } from '@/lib/socket/server';
import type { CreateUserInput, UpdateUserInput } from '@/lib/validators/users';

interface AuditMeta {
  userId: string;
  portId: string;
  ipAddress: string;
  userAgent: string;
}

export async function listUsers(portId: string) {
  const rows = await db
    .select({
      userId: userPortRoles.userId,
      displayName: userProfiles.displayName,
      email: user.email,
      phone: userProfiles.phone,
      isActive: userProfiles.isActive,
      isSuperAdmin: userProfiles.isSuperAdmin,
      lastLoginAt: userProfiles.lastLoginAt,
      roleId: roles.id,
      roleName: roles.name,
      assignedAt: userPortRoles.createdAt,
    })
    .from(userPortRoles)
    .innerJoin(userProfiles, eq(userPortRoles.userId, userProfiles.userId))
    .innerJoin(user, eq(userPortRoles.userId, user.id))
    .innerJoin(roles, eq(userPortRoles.roleId, roles.id))
    .where(eq(userPortRoles.portId, portId))
    .orderBy(userProfiles.displayName);

  return rows.map((row) => ({
    userId: row.userId,
    displayName: row.displayName,
    email: row.email,
    phone: row.phone,
    isActive: row.isActive,
    isSuperAdmin: row.isSuperAdmin,
    lastLoginAt: row.lastLoginAt,
    role: { id: row.roleId, name: row.roleName },
    assignedAt: row.assignedAt,
  }));
}

export async function getUser(userId: string, portId: string) {
  const profile = await db.query.userProfiles.findFirst({
    where: eq(userProfiles.userId, userId),
  });
  if (!profile) throw new NotFoundError('User');

  const authUser = await db.query.user.findFirst({
    where: eq(user.id, userId),
  });

  const portRole = await db.query.userPortRoles.findFirst({
    where: and(eq(userPortRoles.userId, userId), eq(userPortRoles.portId, portId)),
    with: { role: true },
  });
  if (!portRole) throw new NotFoundError('User not assigned to this port');

  return {
    userId: profile.userId,
    displayName: profile.displayName,
    email: authUser?.email ?? '',
    phone: profile.phone,
    isActive: profile.isActive,
    isSuperAdmin: profile.isSuperAdmin,
    lastLoginAt: profile.lastLoginAt,
    avatarUrl: profile.avatarUrl,
    preferences: profile.preferences,
    role: { id: portRole.role.id, name: portRole.role.name },
    residentialAccess: portRole.residentialAccess,
    createdAt: profile.createdAt,
  };
}

export async function createUser(portId: string, data: CreateUserInput, meta: AuditMeta) {
  // Check email uniqueness
  const existingUser = await db.query.user.findFirst({
    where: eq(user.email, data.email.toLowerCase()),
  });
  if (existingUser) {
    throw new ConflictError('A user with this email already exists');
  }

  // Validate role exists
  const role = await db.query.roles.findFirst({
    where: eq(roles.id, data.roleId),
  });
  if (!role) throw new ValidationError('Invalid role ID');

  // Create Better Auth user
  const authResult = await auth.api.signUpEmail({
    body: {
      email: data.email.toLowerCase(),
      password: data.password,
      name: data.name,
    },
  });

  const newUserId = authResult.user.id;

  // Create CRM profile
  await db.insert(userProfiles).values({
    userId: newUserId,
    displayName: data.displayName,
    phone: data.phone ?? null,
  });

  // Assign to port with role
  await db.insert(userPortRoles).values({
    userId: newUserId,
    portId,
    roleId: data.roleId,
    residentialAccess: data.residentialAccess ?? false,
    assignedBy: meta.userId,
  });

  void createAuditLog({
    userId: meta.userId,
    portId,
    action: 'create',
    entityType: 'user',
    entityId: newUserId,
    newValue: { email: data.email, displayName: data.displayName, role: role.name },
    ipAddress: meta.ipAddress,
    userAgent: meta.userAgent,
  });

  emitToRoom(`port:${portId}`, 'system:alert', {
    alertType: 'user:created',
    message: `User "${data.displayName}" added`,
    severity: 'info',
  });

  return getUser(newUserId, portId);
}

export async function updateUser(
  userId: string,
  portId: string,
  data: UpdateUserInput,
  meta: AuditMeta,
) {
  const profile = await db.query.userProfiles.findFirst({
    where: eq(userProfiles.userId, userId),
  });
  if (!profile) throw new NotFoundError('User');

  const portRole = await db.query.userPortRoles.findFirst({
    where: and(eq(userPortRoles.userId, userId), eq(userPortRoles.portId, portId)),
  });
  if (!portRole) throw new NotFoundError('User not assigned to this port');

  // Update profile fields
  const profileUpdates: Record<string, unknown> = { updatedAt: new Date() };
  if (data.displayName !== undefined) profileUpdates.displayName = data.displayName;
  if (data.phone !== undefined) profileUpdates.phone = data.phone;
  if (data.isActive !== undefined) profileUpdates.isActive = data.isActive;

  if (Object.keys(profileUpdates).length > 1) {
    await db.update(userProfiles).set(profileUpdates).where(eq(userProfiles.userId, userId));
  }

  // Update role assignment + per-user toggles
  const portRoleUpdates: Record<string, unknown> = {};
  if (data.roleId && data.roleId !== portRole.roleId) {
    const newRole = await db.query.roles.findFirst({
      where: eq(roles.id, data.roleId),
    });
    if (!newRole) throw new ValidationError('Invalid role ID');
    portRoleUpdates.roleId = data.roleId;
    portRoleUpdates.assignedBy = meta.userId;
  }
  if (
    data.residentialAccess !== undefined &&
    data.residentialAccess !== portRole.residentialAccess
  ) {
    portRoleUpdates.residentialAccess = data.residentialAccess;
  }
  if (Object.keys(portRoleUpdates).length > 0) {
    await db
      .update(userPortRoles)
      .set(portRoleUpdates)
      .where(and(eq(userPortRoles.userId, userId), eq(userPortRoles.portId, portId)));
  }

  void createAuditLog({
    userId: meta.userId,
    portId,
    action: 'update',
    entityType: 'user',
    entityId: userId,
    oldValue: {
      displayName: profile.displayName,
      isActive: profile.isActive,
      roleId: portRole.roleId,
    },
    newValue: data,
    ipAddress: meta.ipAddress,
    userAgent: meta.userAgent,
  });

  emitToRoom(`port:${portId}`, 'system:alert', {
    alertType: 'user:updated',
    message: `User "${data.displayName ?? profile.displayName}" updated`,
    severity: 'info',
  });

  return getUser(userId, portId);
}

export async function removeUserFromPort(userId: string, portId: string, meta: AuditMeta) {
  const portRole = await db.query.userPortRoles.findFirst({
    where: and(eq(userPortRoles.userId, userId), eq(userPortRoles.portId, portId)),
  });
  if (!portRole) throw new NotFoundError('User not assigned to this port');

  // Prevent removing yourself
  if (userId === meta.userId) {
    throw new ValidationError('Cannot remove yourself from the port');
  }

  await db
    .delete(userPortRoles)
    .where(and(eq(userPortRoles.userId, userId), eq(userPortRoles.portId, portId)));

  const profile = await db.query.userProfiles.findFirst({
    where: eq(userProfiles.userId, userId),
  });

  void createAuditLog({
    userId: meta.userId,
    portId,
    action: 'delete',
    entityType: 'user',
    entityId: userId,
    oldValue: { displayName: profile?.displayName, roleId: portRole.roleId },
    ipAddress: meta.ipAddress,
    userAgent: meta.userAgent,
  });

  emitToRoom(`port:${portId}`, 'system:alert', {
    alertType: 'user:removed',
    message: `User "${profile?.displayName}" removed from port`,
    severity: 'info',
  });
}
