import * as _ from 'lodash';
import { RoleAuthor } from '@/entities/role-author.entity';
import { AppRole } from '@/entities/role.entity';
import { User } from '@/entities/user.entity';
import CoreService from '@/system/core/core-service';
import { SystemLang } from '@/system/lang/system.lang';
import AppResponse from '@/system/response/ktq-response';
import { Constant } from '@/ultils/constant';
import { removeFalsy } from '@/ultils/fn';
import { plainToClass } from '@nestjs/class-transformer';
import { BadRequestException, Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import * as bcrypt from 'bcrypt';
import { Request } from 'express';
import {
  FilterOperator,
  FilterSuffix,
  paginate,
  PaginateQuery,
} from 'nestjs-paginate';
import { Column } from 'nestjs-paginate/lib/helper';
import { In, Repository } from 'typeorm';
import { MediasService } from '../medias/medias.service';
import { CreateUserDto } from './dtos/create-user.dto';
import { UpdateUserDto } from './dtos/update-user.dto';

@Injectable()
export class UsersService extends CoreService<User> {
  constructor(
    @InjectRepository(User)
    readonly repo: Repository<User>,
    @InjectRepository(RoleAuthor)
    readonly roleAuthRepo: Repository<RoleAuthor>,
    private readonly mediasService: MediasService,
  ) {
    super(
      repo,
      {
        sortableColumns: [
          'id',
          'email',
          'created_at',
          'updated_at',
          'first_name',
          'last_name',
        ],
        searchableColumns: ['email', 'id', 'first_name', 'last_name'],
        filterableColumns: {
          created_at: true,
          first_name: true,
          last_name: true,
          email: true,
        },
      },
      User,
    );
  }

  protected async beforeUpdate(
    id: number,
    dataRequest: UpdateUserDto,
    entity: User,
    req: Request,
  ): Promise<boolean> {
    const user = req[Constant.ATTR_REQ_DATA];

    // Tìm xem user đang được cập nhật có vai trò ROOT hay không
    const rootRoleInUser = await this.roleAuthRepo.findOne({
      where: {
        author_id: String(entity.id),
        author_table: User.table_name,
        role: {
          role_key: AppRole.ROOT,
        },
      },
      relations: {
        role: true,
      },
      select: { id: true, role: { id: true } },
    });

    // Lấy thông tin role có key là ROOT để phục vụ kiểm tra thêm
    const rootRole = await this.roleAuthRepo.findOne({
      where: {
        role: { role_key: AppRole.ROOT },
      },
      relations: { role: true },
      select: { id: true, role: { id: true } },
    });

    // ====== Trường hợp user KHÔNG có vai trò ROOT ======
    if (!rootRoleInUser) {
      // Nếu cố gắng gán vai trò ROOT cho user → không cho phép
      if (dataRequest.roles.includes(rootRole.role.id)) {
        throw new BadRequestException(
          AppResponse.toResponse(null, {
            message: SystemLang.getCustomText({
              en: 'Cannot create a user with ROOT role.',
              vi: 'Không thể chủ động tạo một người dùng có vai trò ROOT.',
            }),
          }),
        );
      }

      // Nếu bật login nhưng user chưa có mật khẩu và cũng không truyền mật khẩu mới
      if (
        dataRequest.is_login_enabled &&
        !entity.password &&
        !dataRequest.password
      ) {
        throw new BadRequestException(
          AppResponse.toResponse(null, {
            message: SystemLang.getCustomText({
              en: 'Password is required to enable login.',
              vi: 'Bạn phải thiết lập mật khẩu để bật đăng nhập.',
            }),
          }),
        );
      }

      // Nếu không có ROOT và không thêm ROOT → cho phép update
      return true;
    }

    // ====== Trường hợp user CÓ vai trò ROOT ======

    // Không cho phép người khác update tài khoản ROOT
    if (user.id !== entity.id) {
      throw new BadRequestException(
        AppResponse.toResponse(null, {
          message: SystemLang.getCustomText({
            en: 'Account with ROOT role cannot be updated by other users.',
            vi: 'Tài khoản root không thể được cập nhật bởi người dùng khác.',
          }),
        }),
      );
    }

    // Không cho phép xóa role ROOT khỏi user
    if (!dataRequest.roles.includes(rootRoleInUser.role.id)) {
      throw new BadRequestException(
        AppResponse.toResponse(null, {
          message: SystemLang.getCustomText({
            en: 'You cannot remove the ROOT role from a user.',
            vi: 'Không thể gỡ bỏ vai trò ROOT khỏi người dùng.',
          }),
        }),
      );
    }

    // Không cho phép vô hiệu hóa đăng nhập của user có ROOT role
    if (!dataRequest.is_login_enabled) {
      throw new BadRequestException(
        AppResponse.toResponse(null, {
          message: SystemLang.getCustomText({
            en: 'You cannot disable login for a user with ROOT role.',
            vi: 'Không thể tắt đăng nhập cho người dùng có vai trò ROOT.',
          }),
        }),
      );
    }

    // Nếu user có ROOT role → buộc chỉ giữ đúng vai trò ROOT duy nhất
    dataRequest.roles = [rootRoleInUser.role.id];

    return true;
  }

  protected async afterValidated(
    data: UpdateUserDto,
    validated: Partial<User>,
    entity: User,
    req: Request,
  ): Promise<void> {
    // Nếu bật đăng nhập và có mật khẩu → mã hóa mật khẩu
    if (data.is_login_enabled && data.password) {
      validated.password = await bcrypt.hash(data.password, 10);
    }

    if (data.avatar) {
      const result = await this.mediasService.saveBase64Image(
        validated.avatar as string,
        'avatar',
        `medias/avatars/${entity.username}`,
      );
      validated.avatar = result.filename;

      if (entity.avatar) {
        // Nếu đã có avatar cũ, xóa avatar cũ
        await this.mediasService.removeMediasFolder(
          `${entity.username}/${entity.avatar}` as string,
          'avatars',
        );
      }
    }
  }

  protected async afterUpdate(
    id: number,
    data: UpdateUserDto,
    dataUpdate: Partial<User>,
    entity: User,
    req: Request,
  ): Promise<void> {
    // Nếu avatar được cập nhật thành null, xóa avatar cũ
    if (entity.avatar && dataUpdate.avatar === null) {
      await this.mediasService.removeMediasFolder(
        `${entity.username}/${entity.avatar}` as string,
        'avatars',
      );
    }

    const previousRoles = await this.roleAuthRepo.find({
      where: {
        author_id: String(entity.id),
        author_table: User.table_name,
      },
      relations: {
        role: true,
      },
    });

    const previousRolesIDs = previousRoles.map((ra) => ra.role.id);

    const newRoles = data.roles || [];

    // Role mới được thêm vào
    const newRolesAdded = _.difference(newRoles, previousRolesIDs);

    // Role bị gỡ bỏ
    const rolesRemoved = _.difference(previousRolesIDs, newRoles);

    if (newRolesAdded.length > 0) {
      // Thêm role mới
      await this.roleAuthRepo.save(
        newRolesAdded.map((roleId) => {
          const roleAuthor = new RoleAuthor();
          roleAuthor.author_id = String(entity.id);
          roleAuthor.author_table = User.table_name;
          roleAuthor.role_id = roleId;
          return roleAuthor;
        }),
      );
    }

    if (rolesRemoved.length > 0) {
      // Xóa role đã bị gỡ bỏ
      await this.roleAuthRepo.delete({
        author_id: String(entity.id),
        author_table: User.table_name,
        role_id: In(rolesRemoved),
      });
    }
  }

  protected async beforeDelete(
    id: number,
    data: Partial<User>,
    req: Request,
  ): Promise<boolean> {
    const user = req[Constant.ATTR_REQ_DATA];

    if (user.id === data.id) {
      throw new BadRequestException(
        AppResponse.toResponse(null, {
          message: SystemLang.getCustomText({
            en: 'You cannot delete the currently logged-in user.',
            vi: 'Không thể xóa người dùng hiện đang đăng nhập.',
          }),
        }),
      );
    }

    const { roles } = await this._attachRolesToUser(data as User);

    if (roles.some((role) => role.role_key === AppRole.ROOT)) {
      throw new BadRequestException(
        AppResponse.toResponse(null, {
          message: SystemLang.getCustomText({
            en: 'You cannot delete a user with the ROOT role.',
            vi: 'Không thể xóa người dùng có vai trò ROOT.',
          }),
        }),
      );
    }

    return true;
  }

  protected async beforeBulkDelete(
    ids: number[],
    data: User[],
    req?: Request,
  ): Promise<boolean> {
    const user = req[Constant.ATTR_REQ_DATA];

    if (ids.includes(user.id)) {
      throw new BadRequestException(
        AppResponse.toResponse(null, {
          message: SystemLang.getCustomText({
            en: 'You cannot delete the currently logged-in user.',
            vi: 'Không thể xóa người dùng hiện đang đăng nhập.',
          }),
        }),
      );
    }

    if (
      data.some((user) =>
        user.roles.some((role) => role.role_key === AppRole.ROOT),
      )
    ) {
      throw new BadRequestException(
        AppResponse.toResponse(null, {
          message: SystemLang.getCustomText({
            en: 'You cannot delete users with the ROOT role.',
            vi: 'Không thể xóa người dùng có vai trò ROOT.',
          }),
        }),
      );
    }

    return true;
  }

  protected async afterDelete(id: number, data: Partial<User>, req?: Request) {
    // Xóa avatar nếu có
    if (data.avatar) {
      await this.mediasService.removeMediasFolder(
        `${data.username}/${data.avatar}` as string,
        'avatars',
      );
    }

    // Xóa role author liên quan đến user này
    await this.roleAuthRepo.delete({
      author_id: String(id),
      author_table: User.table_name,
    });
  }

  protected async afterBulkDelete(ids: number[], data: User[], req?: Request) {
    // Xóa avatar của tất cả người dùng bị xóa
    for (const user of data) {
      if (user.avatar) {
        await this.mediasService.removeMediasFolder(
          `${user.username}/${user.avatar}` as string,
          'avatars',
        );
      }
    }

    // Xóa role author liên quan đến các user này
    await this.roleAuthRepo.delete({
      author_id: In(ids.map((id) => String(id))),
      author_table: User.table_name,
    });
  }

  async create({ roles, ...data }: CreateUserDto) {
    const dummy: Partial<User> = { ...data };

    if (data.is_login_enabled) {
      dummy.password = await bcrypt.hash(data.password, 10);
    } else {
      dummy.password = '';
    }

    if (data.avatar) {
      const result = await this.mediasService.saveBase64Image(
        data.avatar as string,
        'avatar',
        `medias/avatars/${data.username}`,
      );
      dummy.avatar = result.filename;
    }

    const result = await this.repo.save(dummy);

    if (!result)
      throw new BadRequestException(
        AppResponse.toResponse(null, {
          message: SystemLang.getText('messages', 'create_failed'),
        }),
      );

    await this.roleAuthRepo.save(
      roles.map((roleId) => {
        const roleAuthor = new RoleAuthor();
        roleAuthor.author_id = String(result.id);
        roleAuthor.author_table = User.table_name;
        roleAuthor.role_id = roleId;
        return roleAuthor;
      }),
    );

    return AppResponse.toResponse(plainToClass(User, result), {
      message: SystemLang.getText('messages', 'create_success'),
    });
  }

  private async _attachRolesToUsers(users: User[]): Promise<User[]> {
    const result = [];

    for (const user of users) {
      const roleAuth = await this.roleAuthRepo.find({
        where: {
          author_id: String(user.id),
          author_table: User.table_name,
        },
        relations: {
          role: true,
        },
      });

      const roles = roleAuth.map((ra) => ra.role);

      result.push({
        ...user,
        roles,
      });
    }

    return result;
  }

  private async _attachRolesToUser(user: User): Promise<User> {
    const roleAuth = await this.roleAuthRepo.find({
      where: {
        author_id: String(user.id),
        author_table: User.table_name,
      },
      relations: {
        role: true,
      },
    });

    const roles = roleAuth.map((ra) => ra.role);

    return { ...user, roles } as User;
  }

  async index(query: PaginateQuery) {
    const filterableColumns: {
      [key in Column<User> | (string & {})]?:
        | (FilterOperator | FilterSuffix)[]
        | true;
    } = {
      id: true,
      username: true,
      fullname: true,
      email: true,
    };

    const data = await paginate(query, this.repo, {
      sortableColumns: [],
      searchableColumns: [],
      defaultLimit: 10,
      filterableColumns,
      defaultSortBy: [],
      maxLimit: 100,
      ...removeFalsy(this.options),
    });

    const newData = await this._attachRolesToUsers(data.data);

    return AppResponse.toPagination<User>(
      { ...data, data: plainToClass(User, newData) },
      true,
      this.dtoClass,
    );
  }
}
