import prisma from '../lib/prisma.js';
import bcrypt from 'bcryptjs';
import { paginate, buildPaginationResponse } from '../utils/helpers.js';

export interface UserFilters {
  search?: string;
  isActive?: boolean;
  roleId?: number;
  page?: number;
  limit?: number;
}

export async function getAllUsers(filters: UserFilters) {
  const { search, isActive, roleId, page = 1, limit = 20 } = filters;
  const { skip, take } = paginate(page, limit);

  const where: Record<string, unknown> = {};

  if (isActive !== undefined) {
    where.isActive = isActive;
  }

  if (roleId) {
    where.roles = { some: { roleId } };
  }

  if (search) {
    where.OR = [
      { email: { contains: search } },
      { firstName: { contains: search } },
      { lastName: { contains: search } },
    ];
  }

  const [users, total] = await Promise.all([
    prisma.user.findMany({
      where,
      skip,
      take,
      orderBy: { createdAt: 'desc' },
      select: {
        id: true,
        email: true,
        firstName: true,
        lastName: true,
        isActive: true,
        isServiceAccount: true,
        customerId: true,
        whatsappNumber: true,
        telegramUsername: true,
        signalNumber: true,
        createdAt: true,
        roles: {
          include: {
            role: {
              include: {
                permissions: true,
              },
            },
          },
        },
      },
    }),
    prisma.user.count({ where }),
  ]);

  // Get hidden role IDs
  const [developerRole, gdprRole, auditOpsRole] = await Promise.all([
    prisma.role.findFirst({ where: { name: 'Developer' } }),
    prisma.role.findFirst({ where: { name: 'DSGVO' } }),
    prisma.role.findFirst({ where: { name: AUDIT_OPS_ROLLE } }),
  ]);

  return {
    users: users.map((u) => {
      const hasDeveloperAccess = developerRole
        ? u.roles.some((ur) => ur.roleId === developerRole.id)
        : false;
      const hasGdprAccess = gdprRole
        ? u.roles.some((ur) => ur.roleId === gdprRole.id)
        : false;
      const hasAuditOpsAccess = auditOpsRole
        ? u.roles.some((ur) => ur.roleId === auditOpsRole.id)
        : false;
      return {
        ...u,
        roles: u.roles.map((r) => r.role),
        hasDeveloperAccess,
        hasGdprAccess,
        hasAuditOpsAccess,
      };
    }),
    pagination: buildPaginationResponse(page, limit, total),
  };
}

export async function getUserById(id: number) {
  const user = await prisma.user.findUnique({
    where: { id },
    select: {
      id: true,
      email: true,
      firstName: true,
      lastName: true,
      isActive: true,
      isServiceAccount: true,
      customerId: true,
      whatsappNumber: true,
      telegramUsername: true,
      signalNumber: true,
      createdAt: true,
      updatedAt: true,
      roles: {
        include: {
          role: {
            include: {
              permissions: {
                include: { permission: true },
              },
            },
          },
        },
      },
    },
  });

  if (!user) return null;

  const permissions = new Set<string>();
  for (const userRole of user.roles) {
    for (const rolePerm of userRole.role.permissions) {
      permissions.add(
        `${rolePerm.permission.resource}:${rolePerm.permission.action}`
      );
    }
  }

  return {
    ...user,
    roles: user.roles.map((r) => r.role),
    permissions: Array.from(permissions),
  };
}

export async function createUser(data: {
  email: string;
  password: string;
  firstName: string;
  lastName: string;
  roleIds: number[];
  customerId?: number;
  hasDeveloperAccess?: boolean;
  hasGdprAccess?: boolean;
  hasAuditOpsAccess?: boolean;
  whatsappNumber?: string;
  telegramUsername?: string;
  signalNumber?: string;
}) {
  const hashedPassword = await bcrypt.hash(data.password, 10);

  const user = await prisma.user.create({
    data: {
      email: data.email,
      password: hashedPassword,
      firstName: data.firstName,
      lastName: data.lastName,
      customerId: data.customerId,
      whatsappNumber: data.whatsappNumber || null,
      telegramUsername: data.telegramUsername || null,
      signalNumber: data.signalNumber || null,
      roles: {
        create: data.roleIds.map((roleId) => ({ roleId })),
      },
    },
    select: {
      id: true,
      email: true,
      firstName: true,
      lastName: true,
      isActive: true,
      isServiceAccount: true,
      customerId: true,
      roles: {
        include: { role: true },
      },
    },
  });

  // Entwicklerzugriff setzen falls aktiviert
  if (data.hasDeveloperAccess) {
    await setUserDeveloperAccess(user.id, true);
  }

  // DSGVO-Zugriff setzen falls aktiviert
  if (data.hasGdprAccess) {
    await setUserGdprAccess(user.id, true);
  }

  if (data.hasAuditOpsAccess) {
    await setUserAuditOpsAccess(user.id, true);
  }

  return user;
}

export async function updateUser(
  id: number,
  data: {
    email?: string;
    password?: string;
    firstName?: string;
    lastName?: string;
    isActive?: boolean;
    isServiceAccount?: boolean;
    roleIds?: number[];
    customerId?: number;
    hasDeveloperAccess?: boolean;
    hasGdprAccess?: boolean;
    hasAuditOpsAccess?: boolean;
    whatsappNumber?: string;
    telegramUsername?: string;
    signalNumber?: string;
  }
) {
  const { roleIds, password, hasDeveloperAccess, hasGdprAccess, hasAuditOpsAccess, ...userData } = data;

  // Check if this would remove the last admin
  const isBeingDeactivated = userData.isActive === false;
  const rolesAreBeingChanged = roleIds !== undefined;

  if (isBeingDeactivated || rolesAreBeingChanged) {
    // Check if user currently has admin permissions
    const currentUser = await prisma.user.findUnique({
      where: { id },
      include: {
        roles: {
          include: {
            role: {
              include: {
                permissions: {
                  include: { permission: true },
                },
              },
            },
          },
        },
      },
    });

    const isCurrentlyAdmin = currentUser?.roles.some((ur) =>
      ur.role.permissions.some(
        (rp) => rp.permission.resource === 'users' && rp.permission.action === 'delete'
      )
    );

    if (isCurrentlyAdmin) {
      // Check if user will still be admin after role change
      let willStillBeAdmin = false;
      if (rolesAreBeingChanged) {
        const newRoles = await prisma.role.findMany({
          where: { id: { in: roleIds } },
          include: {
            permissions: {
              include: { permission: true },
            },
          },
        });
        willStillBeAdmin = newRoles.some((role) =>
          role.permissions.some(
            (rp) => rp.permission.resource === 'users' && rp.permission.action === 'delete'
          )
        );
      } else {
        willStillBeAdmin = true; // Roles not being changed
      }

      // If user is losing admin status or being deactivated, check for other admins
      if (!willStillBeAdmin || isBeingDeactivated) {
        const otherAdminCount = await prisma.user.count({
          where: {
            id: { not: id },
            isActive: true,
            roles: {
              some: {
                role: {
                  permissions: {
                    some: {
                      permission: {
                        resource: 'users',
                        action: 'delete',
                      },
                    },
                  },
                },
              },
            },
          },
        });

        if (otherAdminCount === 0) {
          if (isBeingDeactivated) {
            throw new Error(
              'Dieser Benutzer ist der letzte Administrator und kann nicht deaktiviert werden'
            );
          } else {
            throw new Error(
              'Die Admin-Rolle kann nicht entfernt werden, da dies der letzte Administrator ist'
            );
          }
        }
      }
    }
  }

  // Hash password if provided
  if (password) {
    (userData as Record<string, unknown>).password = await bcrypt.hash(password, 10);
  }

  // Prüfen ob Rollen geändert werden (für Zwangslogout)
  let rolesChanged = false;
  if (roleIds !== undefined) {
    const currentRoles = await prisma.userRole.findMany({
      where: { userId: id },
      select: { roleId: true },
    });
    const currentRoleIds = currentRoles.map((r) => r.roleId).sort();
    const newRoleIds = [...roleIds].sort();
    rolesChanged =
      currentRoleIds.length !== newRoleIds.length ||
      !currentRoleIds.every((id, i) => id === newRoleIds[i]);
  }

  // Update user - bei Rollenänderung Token invalidieren
  await prisma.user.update({
    where: { id },
    data: {
      ...userData,
      // Token invalidieren wenn Rollen geändert werden
      ...(rolesChanged && { tokenInvalidatedAt: new Date() }),
    },
  });

  // Update roles if provided
  if (roleIds) {
    await prisma.userRole.deleteMany({ where: { userId: id } });
    await prisma.userRole.createMany({
      data: roleIds.map((roleId) => ({ userId: id, roleId })),
    });
  }

  // Handle developer access
  if (hasDeveloperAccess !== undefined) {
    await setUserDeveloperAccess(id, hasDeveloperAccess);
  }

  // Handle GDPR access
  if (hasGdprAccess !== undefined) {
    await setUserGdprAccess(id, hasGdprAccess);
  }

  if (hasAuditOpsAccess !== undefined) {
    await setUserAuditOpsAccess(id, hasAuditOpsAccess);
  }

  return getUserById(id);
}

// Helper to set developer access for a user
async function setUserDeveloperAccess(userId: number, enabled: boolean) {
  // Get or create developer:access permission
  let developerPerm = await prisma.permission.findFirst({
    where: { resource: 'developer', action: 'access' },
  });

  if (!developerPerm) {
    developerPerm = await prisma.permission.create({
      data: { resource: 'developer', action: 'access' },
    });
  }

  // Get or create Developer role
  let developerRole = await prisma.role.findFirst({
    where: { name: 'Developer' },
  });

  if (!developerRole) {
    developerRole = await prisma.role.create({
      data: {
        name: 'Developer',
        description: 'Entwicklerzugriff auf Datenbanktools',
        permissions: {
          create: [{ permissionId: developerPerm.id }],
        },
      },
    });
  }

  // Check if user already has Developer role
  const hasRole = await prisma.userRole.findFirst({
    where: { userId, roleId: developerRole.id },
  });

  if (enabled && !hasRole) {
    await prisma.userRole.create({
      data: { userId, roleId: developerRole.id },
    });
    // Token invalidieren bei Rechteänderung
    await prisma.user.update({
      where: { id: userId },
      data: { tokenInvalidatedAt: new Date() },
    });
  } else if (!enabled && hasRole) {
    await prisma.userRole.delete({
      where: { userId_roleId: { userId, roleId: developerRole.id } },
    });
    // Token invalidieren bei Rechteänderung
    await prisma.user.update({
      where: { id: userId },
      data: { tokenInvalidatedAt: new Date() },
    });
  }
}

/**
 * Name der versteckten Rolle fuer eingreifende Audit-Rechte.
 *
 * Getrennt von `DSGVO`, weil Aufsicht und Eingriff nicht dieselbe Rolle sein
 * duerfen: Wer das Protokoll beaufsichtigt, darf seine Beweisgrundlage nicht
 * ersetzen koennen (Pentest R186). Die Rolle wird von `sync-roles.ts` beim
 * Containerstart angelegt.
 */
export const AUDIT_OPS_ROLLE = 'Audit-Betrieb';

// Helper to set GDPR access for a user
async function setUserGdprAccess(userId: number, enabled: boolean) {
  // Get or create DSGVO role
  let gdprRole = await prisma.role.findFirst({
    where: { name: 'DSGVO' },
  });

  if (!gdprRole) {
    // Rechte-Satz identisch zu sync-roles.ts: gdpr komplett, vom Audit-
    // Protokoll nur LESEN und EXPORTIEREN. Ohne `audit:admin` - sonst
    // brächte dieser Notfallpfad genau die Bündelung zurück, die wir gerade
    // aufgelöst haben (zwei Listen, die dasselbe bedeuten sollen, laufen
    // auseinander).
    const gdprPermissions = await prisma.permission.findMany({
      where: {
        OR: [
          { resource: 'gdpr' },
          { resource: 'audit', action: { in: ['read', 'export'] } },
        ],
      },
    });

    gdprRole = await prisma.role.create({
      data: {
        name: 'DSGVO',
        description: 'DSGVO-Zugriff: Audit-Logs lesen und Datenschutz-Verwaltung',
        permissions: {
          create: gdprPermissions.map((p) => ({ permissionId: p.id })),
        },
      },
    });
  }

  // Check if user already has DSGVO role
  const hasRole = await prisma.userRole.findFirst({
    where: { userId, roleId: gdprRole.id },
  });

  if (enabled && !hasRole) {
    await prisma.userRole.create({
      data: { userId, roleId: gdprRole.id },
    });
    await prisma.user.update({
      where: { id: userId },
      data: { tokenInvalidatedAt: new Date() },
    });
  } else if (!enabled && hasRole) {
    await prisma.userRole.delete({
      where: { userId_roleId: { userId, roleId: gdprRole.id } },
    });
    await prisma.user.update({
      where: { id: userId },
      data: { tokenInvalidatedAt: new Date() },
    });
  }
}

/**
 * Eingreifende Audit-Rechte setzen: versiegeln, neu berechnen, aufraeumen,
 * Aufbewahrung aendern. Bewusst getrennt vom DSGVO-Haken (siehe
 * `AUDIT_OPS_ROLLE`).
 */
async function setUserAuditOpsAccess(userId: number, enabled: boolean) {
  let rolle = await prisma.role.findFirst({ where: { name: AUDIT_OPS_ROLLE } });

  if (!rolle) {
    // Rechte-Satz identisch zu sync-roles.ts.
    const rechte = await prisma.permission.findMany({
      where: { resource: 'audit', action: { in: ['read', 'admin'] } },
    });
    rolle = await prisma.role.create({
      data: {
        name: AUDIT_OPS_ROLLE,
        description: 'Darf das Audit-Protokoll versiegeln, aufräumen und die Aufbewahrung ändern',
        permissions: { create: rechte.map((p) => ({ permissionId: p.id })) },
      },
    });
  }

  const hatRolle = await prisma.userRole.findFirst({
    where: { userId, roleId: rolle.id },
  });

  if (enabled && !hatRolle) {
    await prisma.userRole.create({ data: { userId, roleId: rolle.id } });
    // Token invalidieren bei Rechteaenderung
    await prisma.user.update({
      where: { id: userId },
      data: { tokenInvalidatedAt: new Date() },
    });
  } else if (!enabled && hatRolle) {
    await prisma.userRole.delete({
      where: { userId_roleId: { userId, roleId: rolle.id } },
    });
    await prisma.user.update({
      where: { id: userId },
      data: { tokenInvalidatedAt: new Date() },
    });
  }
}

export async function deleteUser(id: number) {
  // Check if user is an admin
  const user = await prisma.user.findUnique({
    where: { id },
    include: {
      roles: {
        include: {
          role: {
            include: {
              permissions: {
                include: { permission: true },
              },
            },
          },
        },
      },
    },
  });

  if (!user) {
    throw new Error('Benutzer nicht gefunden');
  }

  // Check if user has admin permissions (users:delete means admin)
  const isAdmin = user.roles.some((ur) =>
    ur.role.permissions.some(
      (rp) => rp.permission.resource === 'users' && rp.permission.action === 'delete'
    )
  );

  if (isAdmin) {
    // Count other admins (users with users:delete permission)
    const adminCount = await prisma.user.count({
      where: {
        id: { not: id },
        isActive: true,
        roles: {
          some: {
            role: {
              permissions: {
                some: {
                  permission: {
                    resource: 'users',
                    action: 'delete',
                  },
                },
              },
            },
          },
        },
      },
    });

    if (adminCount === 0) {
      throw new Error(
        'Dieser Benutzer ist der letzte Administrator und kann nicht gelöscht werden'
      );
    }
  }

  return prisma.user.delete({ where: { id } });
}

// Role operations
export async function getAllRoles() {
  return prisma.role.findMany({
    include: {
      permissions: {
        include: { permission: true },
      },
      _count: {
        select: { users: true },
      },
    },
    orderBy: { name: 'asc' },
  });
}

export async function getRoleById(id: number) {
  return prisma.role.findUnique({
    where: { id },
    include: {
      permissions: {
        include: { permission: true },
      },
    },
  });
}

export async function createRole(data: {
  name: string;
  description?: string;
  permissionIds: number[];
}) {
  return prisma.role.create({
    data: {
      name: data.name,
      description: data.description,
      permissions: {
        create: data.permissionIds.map((permissionId) => ({ permissionId })),
      },
    },
    include: {
      permissions: {
        include: { permission: true },
      },
    },
  });
}

export async function updateRole(
  id: number,
  data: {
    name?: string;
    description?: string;
    permissionIds?: number[];
  }
) {
  const { permissionIds, ...roleData } = data;

  await prisma.role.update({
    where: { id },
    data: roleData,
  });

  if (permissionIds) {
    await prisma.rolePermission.deleteMany({ where: { roleId: id } });
    await prisma.rolePermission.createMany({
      data: permissionIds.map((permissionId) => ({ roleId: id, permissionId })),
    });
  }

  return getRoleById(id);
}

export async function deleteRole(id: number) {
  // Check if role is assigned to any users
  const count = await prisma.userRole.count({ where: { roleId: id } });
  if (count > 0) {
    throw new Error(
      `Rolle kann nicht gelöscht werden, da sie ${count} Benutzern zugewiesen ist`
    );
  }

  return prisma.role.delete({ where: { id } });
}

// Permission operations
export async function getAllPermissions() {
  return prisma.permission.findMany({
    orderBy: [{ resource: 'asc' }, { action: 'asc' }],
  });
}
