import { Injectable, NotFoundException } from "@nestjs/common"
import { RoleRepository } from "../repositories/role.repository"
import { Role } from "../entities/role.entity"
import { CreateRoleDto } from "../dto/create-role.dto"
import {
  generatePassword,
  isEmpty,
  sendEmailNotification,
  successMessage,
} from "src/utils/helpers"
import { failureResponse } from "src/common/response/response"
import { UpdateRoleDto } from "../dto/update-role.dto"
import { messageKey } from "src/constants/message-keys"
import { FindAllRolesDto } from "../dto/find-all-roles.dto"
import { code } from "src/common/response/response.code"
import { InjectRepository } from "@nestjs/typeorm"
import { In, Repository } from "typeorm"
import { Department } from "../../department/entities/department.entity"
import { BusinessVertical } from "../../business-verticals/entities/business-verticals.entity"
import { TeamMember } from "../../team-member/entities/team_member.entity"
import { UserLogin } from "../../auth/entities/user-login.entity"
import { Auth } from "../../auth/entities/auth.entity"
import * as bcrypt from "bcrypt"
import { createUserEmail } from "src/common/emails/templates/login-credentials"

@Injectable()
export class RoleService {
  constructor(
    private readonly roleRepository: RoleRepository,

    @InjectRepository(Department)
    private readonly departmentRepo: Repository<Department>,

    @InjectRepository(BusinessVertical)
    private readonly businessVerticalRepo: Repository<BusinessVertical>,

    @InjectRepository(Role)
    private readonly roleModelRepository: Repository<Role>,

    @InjectRepository(TeamMember)
    private readonly teamMemberRepository: Repository<TeamMember>,

    @InjectRepository(Auth)
    private readonly authRepository: Repository<Auth>,

    @InjectRepository(UserLogin)
    private readonly userLoginRepository: Repository<UserLogin>,
  ) {}

  async checkRoleExist(name: string, excludeId?: number): Promise<boolean> {
    const whereCondition: any = {}
    const wherelowerCondition: any = {
      name,
    }

    if (excludeId) {
      whereCondition["id"] = {
        not: excludeId,
      }
    }

    const currency = await this.roleRepository.getByParams({
      where: whereCondition,
      whereLower: wherelowerCondition,
      findOne: true,
    })

    return !isEmpty(currency)
  }

  async createRole(createRoleDto: CreateRoleDto) {
    const isRoleExist = await this.checkRoleExist(createRoleDto.name)

    if (isRoleExist) {
      return failureResponse(400, "Role already exist")
    }

    const role = new Role()
    role.name = createRoleDto.name
    role.status = createRoleDto.status
    role.description = createRoleDto.description
    role.parent_role_id = createRoleDto.parent_role_id
    role.is_web_login = createRoleDto.is_web_login

    if (createRoleDto.departments?.length > 0) {
      role.departments = await this.departmentRepo.findBy({
        id: In(createRoleDto.departments),
      })
    }

    if (createRoleDto.business_verticals?.length > 0) {
      role.business_verticals = await this.businessVerticalRepo.findBy({
        id: In(createRoleDto.business_verticals),
      })
    }

    return await this.roleRepository.save(role)
  }

  async findAllRoles(params: FindAllRolesDto) {
    return await this.roleRepository.findAllWithFilters(params)
  }

  async findOneRole(id: number): Promise<Role> {
    const role = (await this.roleRepository.getByParams({
      where: { id },
      relations: ["departments", "business_verticals", "parent_role"],
      findOne: true,
    })) as Role

    if (!role) {
      throw new Error(messageKey.data_not_found)
    }

    if (role.name === "super admin") {
      const [departments, businessVerticals] = await Promise.all([
        this.departmentRepo.find(),
        this.businessVerticalRepo.find(),
      ])

      role.departments = departments
      role.business_verticals = businessVerticals
    }

    return role
  }

  async updateRole(id: number, updateRoleDto: UpdateRoleDto) {
    const existingRole = await this.roleRepository.getByParams({
      where: { id },
      findOne: true,
      relations: ["departments", "business_verticals"],
    })

    if (!existingRole) {
      return failureResponse(404, messageKey.data_not_found)
    }

    const existingSameNameRole = await this.checkRoleExist(
      updateRoleDto.name,
      id,
    )
    if (existingSameNameRole) {
      return failureResponse(code.BAD_REQUEST, "Role already exist")
    }

    const role = existingRole as Role

    // Check only for REMOVED departments, not added ones
    if (updateRoleDto.departments !== undefined) {
      const currentDepartmentIds = role.departments?.map((d) => d.id) || []
      const newDepartmentIds = updateRoleDto.departments

      // Find which departments are being REMOVED
      const removedDepartmentIds = currentDepartmentIds.filter(
        (deptId) => !newDepartmentIds.includes(deptId),
      )

      // Only check if there are removed departments
      if (removedDepartmentIds.length > 0) {
        const assignedInRemovedDepartments =
          await this.teamMemberRepository.find({
            where: {
              role: { id },
              department: { id: In(removedDepartmentIds) },
            },
          })

        if (assignedInRemovedDepartments.length > 0) {
          return failureResponse(
            code.BAD_REQUEST,
            "Role cannot be updated as it is assigned to existing team members in selected departments or business verticals.",
          )
        }
      }
    }

    // Check only for REMOVED business verticals, not added ones
    if (updateRoleDto.business_verticals !== undefined) {
      const currentBVIds = role.business_verticals?.map((bv) => bv.id) || []
      const newBVIds = updateRoleDto.business_verticals

      // Find which business verticals are being REMOVED
      const removedBVIds = currentBVIds.filter(
        (bvId) => !newBVIds.includes(bvId),
      )

      // Only check if there are removed business verticals
      if (removedBVIds.length > 0) {
        const assignedInRemovedBVs = await this.teamMemberRepository.find({
          where: {
            role: { id },
            business_vertical: { id: In(removedBVIds) },
          },
        })

        if (assignedInRemovedBVs.length > 0) {
          return failureResponse(
            code.BAD_REQUEST,
            "Role cannot be updated as it is assigned to existing team members in selected departments or business verticals.",
          )
        }
      }
    }

    if (
      updateRoleDto.parent_role_id !== undefined &&
      updateRoleDto.parent_role_id !== role.parent_role_id
    ) {
      const isTeamMemberExist = await this.teamMemberRepository.findOne({
        where: {
          role_id: id,
        },
      })

      if (!isEmpty(isTeamMemberExist)) {
        return failureResponse(
          code.BAD_REQUEST,
          messageKey.reporting_role_already_exist,
        )
      }
    }

    role.name = updateRoleDto.name
    role.status = updateRoleDto.status
    role.description = updateRoleDto.description
    role.parent_role_id = updateRoleDto.parent_role_id
    role.is_web_login = updateRoleDto.is_web_login

    if (updateRoleDto.departments?.length) {
      role.departments = await this.departmentRepo.findBy({
        id: In(updateRoleDto.departments),
      })
    } else {
      role.departments = []
    }

    if (updateRoleDto.business_verticals?.length) {
      role.business_verticals = await this.businessVerticalRepo.findBy({
        id: In(updateRoleDto.business_verticals),
      })
    } else {
      role.business_verticals = []
    }

    const updatedRole = await this.roleRepository.save(role)

    if (role.is_web_login === false) {
      const affectedTeamMembers = await this.teamMemberRepository.find({
        where: {
          role: { id: role.id },
        },
        select: ["id"],
      })

      const authUsers = await this.authRepository.find({
        where: {
          team_member_id: In(affectedTeamMembers.map((tm) => tm.id)),
        },
        select: ["id"],
      })

      if (authUsers.length > 0) {
        await this.userLoginRepository.delete({
          user_id: In(authUsers.map((auth) => auth.id)),
        })
      }
    }

    if (role.is_web_login === true) {
      const team_members = (await this.teamMemberRepository.find({
        where: {
          role: { id: role.id },
        },
      })) as TeamMember[]

      for (const team_member of team_members) {
        const password = generatePassword()
        const hashedPassword = await bcrypt.hash(password, 10)
        await this.authRepository.save({
          id: team_member.user_id,
          email: team_member.email,
          password: hashedPassword,
        })

        const emailHTML = createUserEmail(password)
        await sendEmailNotification(
          team_member.email,
          emailHTML,
          "Your account credentials have been created",
        )
      }
    }

    return updatedRole
  }

  async remove(id: number) {
    try {
      const role = (await this.roleRepository.getByParams({
        where: { id },
        findOne: true,
        relations: ["team_member", "child_roles"],
      })) as Role

      if (isEmpty(role)) {
        return failureResponse(code.DATA_NOT_FOUND, messageKey.data_not_found)
      }

      if (role.team_member?.length > 0 || role.child_roles?.length > 0) {
        return failureResponse(
          code.BAD_REQUEST,
          "Cannot delete this role because it has associated team members or parent role",
        )
      }

      await this.roleModelRepository.softDelete(id)

      return successMessage(messageKey.data_removed, { ":data": "role" })
    } catch (error) {
      return failureResponse(code.ERROR, messageKey.something_went_wrong)
    }
  }

  async changeRoleStatus(id: number, status: number): Promise<Role> {
    const role = (await this.roleRepository.getByParams({
      where: { id },
      findOne: true,
    })) as Role

    if (!role) {
      throw new NotFoundException(`Role with ID ${id} not found`)
    }

    role.status = status === 0 ? 0 : 1

    return await this.roleRepository.save(role)
  }

  async rolesDropdown(excludeId: string) {
    return await this.roleRepository.getByParams({
      select: ["id", "name", "is_web_login"],
      relations: ["departments", "business_verticals"],
      where: !isEmpty(excludeId)
        ? {
            id: {
              not: parseInt(excludeId, 10),
            },
          }
        : {},
    })
  }
}
