import {
  BadRequestException,
  ConflictException,
  ForbiddenException,
  Injectable,
  InternalServerErrorException,
  NotFoundException,
  OnModuleInit,
} from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { UserRole } from '@prisma/client';
import * as bcrypt from 'bcrypt';
import { MAX_WORKSPACE_ADMINS } from '../projects/project-scope.constants';
import { PrismaService } from '../prisma/prisma.service';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';

@Injectable()
export class UsersService implements OnModuleInit {
  constructor(
    private readonly prismaService: PrismaService,
    private readonly configService: ConfigService,
  ) {}

  async onModuleInit() {
    await this.ensureAdminUser();
  }

  private async ensureAdminUser() {
    const adminUsername = this.configService.get<string>('ADMIN_USERNAME');
    const adminPassword = this.configService.get<string>('ADMIN_PASSWORD');

    if (!adminUsername || !adminPassword) {
      return;
    }

    const passwordHash = await bcrypt.hash(adminPassword, 12);

    await this.prismaService.user.upsert({
      where: { username: adminUsername },
      update: {
        passwordHash,
        displayName: adminUsername,
        role: UserRole.ADMIN,
      },
      create: {
        username: adminUsername,
        passwordHash,
        displayName: adminUsername,
        role: UserRole.ADMIN,
      },
    });
  }

  private async enforceWorkspaceAdminLimit(workspaceId: string, excludedUserId?: string) {
    const adminCount = await this.prismaService.user.count({
      where: {
        workspaceId,
        role: UserRole.ADMIN,
        ...(excludedUserId
          ? {
              id: {
                not: excludedUserId,
              },
            }
          : {}),
      },
    });

    if (adminCount >= MAX_WORKSPACE_ADMINS) {
      throw new ForbiddenException(
        `Limite atteinte: maximum ${MAX_WORKSPACE_ADMINS} comptes admin par espace`,
      );
    }
  }

  private async getActorOrThrow(actorUserId?: string) {
    if (!actorUserId) {
      return null;
    }

    const actor = await this.prismaService.user.findUnique({
      where: { id: actorUserId },
      select: {
        id: true,
        role: true,
        workspaceId: true,
      },
    });

    if (!actor) {
      throw new NotFoundException('Compte admin introuvable');
    }

    if (actor.role !== UserRole.ADMIN) {
      throw new ForbiddenException('Action reservee aux admins');
    }

    return actor;
  }

  private async resolveCurrentProjectId(userId: string) {
    const membership = await this.prismaService.projectMembership.findUnique({
      where: { userId },
      select: { projectId: true },
    });

    return membership?.projectId ?? null;
  }

  private async assignUserToProject(
    userId: string,
    projectId: string,
    actorUserId?: string,
  ) {
    const [targetUser, targetProject, actor] = await Promise.all([
      this.prismaService.user.findUnique({
        where: { id: userId },
        select: {
          id: true,
          role: true,
          workspaceId: true,
        },
      }),
      this.prismaService.project.findUnique({
        where: { id: projectId },
        select: {
          id: true,
          workspaceId: true,
        },
      }),
      this.getActorOrThrow(actorUserId),
    ]);

    if (!targetUser) {
      throw new NotFoundException('User not found');
    }

    if (!targetProject) {
      throw new NotFoundException('Project not found');
    }

    if (targetUser.role === UserRole.ADMIN) {
      throw new BadRequestException('Un admin ne peut pas etre affecte comme participant');
    }

    if (actor) {
      if (!actor.workspaceId || actor.workspaceId !== targetProject.workspaceId) {
        throw new ForbiddenException('Ce projet appartient a un autre espace');
      }
    }

    if (targetUser.workspaceId && targetUser.workspaceId !== targetProject.workspaceId) {
      throw new ForbiddenException('Cet utilisateur appartient deja a un autre espace');
    }

    if (!targetUser.workspaceId) {
      await this.prismaService.user.update({
        where: { id: targetUser.id },
        data: { workspaceId: targetProject.workspaceId },
      });
    }

    const existingMembership = await this.prismaService.projectMembership.findUnique({
      where: { userId: targetUser.id },
      select: { projectId: true },
    });

    if (existingMembership && existingMembership.projectId !== targetProject.id) {
      await this.prismaService.projectMembership.update({
        where: { userId: targetUser.id },
        data: {
          projectId: targetProject.id,
          assignedById: actor?.id,
        },
      });
      return;
    }

    if (!existingMembership) {
      await this.prismaService.projectMembership.create({
        data: {
          projectId: targetProject.id,
          userId: targetUser.id,
          assignedById: actor?.id,
        },
      });
    }
  }

  async create(createUserDto: CreateUserDto, actorUserId?: string) {
    const passwordHash = await bcrypt.hash(createUserDto.password, 12);
    const actor = await this.getActorOrThrow(actorUserId);
    const role = createUserDto.role ?? UserRole.FAMILY;

    if (role === UserRole.ADMIN && actor?.workspaceId) {
      await this.enforceWorkspaceAdminLimit(actor.workspaceId);
    }

    try {
      const user = await this.prismaService.user.create({
        data: {
          username: createUserDto.username,
          displayName: createUserDto.displayName,
          passwordHash,
          role,
          workspaceId: actor?.workspaceId,
        },
      });

      if (createUserDto.projectId) {
        await this.assignUserToProject(user.id, createUserDto.projectId, actor?.id);
      }

      const currentProjectId = await this.resolveCurrentProjectId(user.id);

      return {
        id: user.id,
        username: user.username,
        displayName: user.displayName,
        profileImageUrl: user.profileImageUrl,
        workspaceId: user.workspaceId,
        currentProjectId,
        role: user.role,
        createdAt: user.createdAt,
      };
    } catch (error: unknown) {
      if (
        typeof error === 'object' &&
        error !== null &&
        'code' in error &&
        error.code === 'P2002'
      ) {
        throw new ConflictException('Username already exists');
      }

      throw new InternalServerErrorException('Could not create user');
    }
  }

  async findByUsername(username: string) {
    return this.prismaService.user.findFirst({
      where: {
        username: {
          equals: username.trim(),
          mode: 'insensitive',
        },
      },
    });
  }

  async list() {
    const users = await this.prismaService.user.findMany({
      orderBy: { createdAt: 'desc' },
      select: {
        id: true,
        username: true,
        displayName: true,
        profileImageUrl: true,
        workspaceId: true,
        role: true,
        createdAt: true,
        projectMembership: {
          select: {
            projectId: true,
          },
        },
      },
    });

    return users.map((user) => ({
      id: user.id,
      username: user.username,
      displayName: user.displayName,
      profileImageUrl: user.profileImageUrl,
      workspaceId: user.workspaceId,
      currentProjectId: user.projectMembership?.projectId ?? null,
      role: user.role,
      createdAt: user.createdAt,
    }));
  }

  async updateById(id: string, updateUserDto: UpdateUserDto, actorUserId?: string) {
    try {
      const actor = await this.getActorOrThrow(actorUserId);

      const data: {
        username?: string;
        passwordHash?: string;
        role?: UserRole;
        displayName?: string;
        workspaceId?: string;
      } = {};

      if (updateUserDto.username) {
        data.username = updateUserDto.username;
      }

      if (updateUserDto.password) {
        data.passwordHash = await bcrypt.hash(updateUserDto.password, 12);
      }

      if (updateUserDto.role) {
        data.role = updateUserDto.role;
      }

      if (updateUserDto.displayName !== undefined) {
        data.displayName = updateUserDto.displayName;
      }

      const existing = await this.prismaService.user.findUnique({ where: { id } });

      if (!existing) {
        throw new NotFoundException('User not found');
      }

      if (actor && actor.workspaceId && existing.workspaceId && actor.workspaceId !== existing.workspaceId) {
        throw new ForbiddenException('Vous ne pouvez gerer que les comptes de votre espace');
      }

      if (actor && actor.workspaceId && !existing.workspaceId) {
        data.workspaceId = actor.workspaceId;
      }

      if (updateUserDto.role === UserRole.ADMIN) {
        const workspaceId = existing.workspaceId ?? actor?.workspaceId;
        if (!workspaceId) {
          throw new BadRequestException('Impossible de promouvoir en admin sans espace associe');
        }

        data.workspaceId = workspaceId;

        if (existing.role !== UserRole.ADMIN) {
          await this.enforceWorkspaceAdminLimit(workspaceId, existing.id);
        }
      }

      if (actorUserId && existing.id === actorUserId && updateUserDto.role === UserRole.FAMILY) {
        throw new ForbiddenException('You cannot demote yourself');
      }

      const user = await this.prismaService.user.update({
        where: { id },
        data,
      });

      return {
        id: user.id,
        username: user.username,
        displayName: user.displayName,
        profileImageUrl: user.profileImageUrl,
        workspaceId: user.workspaceId,
        role: user.role,
        createdAt: user.createdAt,
      };
    } catch (error: unknown) {
      if (
        typeof error === 'object' &&
        error !== null &&
        'code' in error &&
        error.code === 'P2002'
      ) {
        throw new ConflictException('Username already exists');
      }
      if (
        error instanceof ForbiddenException ||
        error instanceof InternalServerErrorException ||
        error instanceof NotFoundException
      ) {
        throw error;
      }
      throw new InternalServerErrorException('Could not update user');
    }
  }

  async assignProjectById(userId: string, projectId: string | undefined, actorUserId?: string) {
    const actor = await this.getActorOrThrow(actorUserId);

    const targetUser = await this.prismaService.user.findUnique({
      where: { id: userId },
      select: {
        id: true,
        role: true,
        workspaceId: true,
        username: true,
        displayName: true,
        profileImageUrl: true,
        createdAt: true,
      },
    });

    if (!targetUser) {
      throw new NotFoundException('User not found');
    }

    if (actor && actor.workspaceId && targetUser.workspaceId && targetUser.workspaceId !== actor.workspaceId) {
      throw new ForbiddenException('Vous ne pouvez gerer que les comptes de votre espace');
    }

    if (!projectId) {
      await this.prismaService.projectMembership.deleteMany({
        where: { userId: targetUser.id },
      });

      return {
        id: targetUser.id,
        username: targetUser.username,
        displayName: targetUser.displayName,
        profileImageUrl: targetUser.profileImageUrl,
        workspaceId: targetUser.workspaceId,
        currentProjectId: null,
        role: targetUser.role,
        createdAt: targetUser.createdAt,
      };
    }

    await this.assignUserToProject(targetUser.id, projectId, actor?.id);
    const currentProjectId = await this.resolveCurrentProjectId(targetUser.id);

    return {
      id: targetUser.id,
      username: targetUser.username,
      displayName: targetUser.displayName,
      profileImageUrl: targetUser.profileImageUrl,
      workspaceId: targetUser.workspaceId,
      currentProjectId,
      role: targetUser.role,
      createdAt: targetUser.createdAt,
    };
  }

  async removeById(id: string, actorUserId?: string) {
    if (actorUserId && id === actorUserId) {
      throw new ForbiddenException('You cannot delete your own account');
    }

    try {
      const actor = await this.getActorOrThrow(actorUserId);
      const target = await this.prismaService.user.findUnique({
        where: { id },
        select: {
          id: true,
          workspaceId: true,
        },
      });

      if (!target) {
        throw new NotFoundException('User not found');
      }

      if (actor && actor.workspaceId && target.workspaceId && actor.workspaceId !== target.workspaceId) {
        throw new ForbiddenException('Vous ne pouvez supprimer que des comptes de votre espace');
      }

      await this.prismaService.user.delete({ where: { id } });
      return { deleted: true };
    } catch (error) {
      if (error instanceof ForbiddenException || error instanceof NotFoundException) {
        throw error;
      }
      throw new InternalServerErrorException('Could not delete user');
    }
  }

  async getProfile(userId: string) {
    const user = await this.prismaService.user.findUnique({ where: { id: userId } });

    if (!user) {
      throw new NotFoundException('User not found');
    }

    return {
      id: user.id,
      username: user.username,
      displayName: user.displayName,
      profileImageUrl: user.profileImageUrl,
      profileBgColor: user.profileBgColor,
      workspaceId: user.workspaceId,
      currentProjectId: await this.resolveCurrentProjectId(user.id),
      role: user.role,
      createdAt: user.createdAt,
    };
  }

  async updateMyProfile(userId: string, displayName?: string) {
    const user = await this.prismaService.user.update({
      where: { id: userId },
      data: { displayName },
    });

    return {
      id: user.id,
      username: user.username,
      displayName: user.displayName,
      profileImageUrl: user.profileImageUrl,
      profileBgColor: user.profileBgColor,
      workspaceId: user.workspaceId,
      currentProjectId: await this.resolveCurrentProjectId(user.id),
      role: user.role,
      createdAt: user.createdAt,
    };
  }

  async updateProfileImage(userId: string, profileImageUrl?: string | null, profileBgColor?: string | null) {
    const data: { profileImageUrl?: string | null; profileBgColor?: string | null } = {};
    if (profileImageUrl !== undefined) data.profileImageUrl = profileImageUrl;
    if (profileBgColor !== undefined) data.profileBgColor = profileBgColor;

    const user = await this.prismaService.user.update({
      where: { id: userId },
      data,
    });

    return {
      id: user.id,
      username: user.username,
      displayName: user.displayName,
      profileImageUrl: user.profileImageUrl,
      profileBgColor: user.profileBgColor,
      workspaceId: user.workspaceId,
      currentProjectId: await this.resolveCurrentProjectId(user.id),
      role: user.role,
      createdAt: user.createdAt,
    };
  }
}