import {
  ConflictException,
  Injectable,
  NotFoundException,
} from '@nestjs/common';
import { AuditAction, AuditEntityType } from '@aechr/shared';
import { Prisma, UserStatus } from '@prisma/client';
import { hash } from 'bcryptjs';
import type { JwtAuthUser } from '../../common/interfaces/jwt-auth-user.interface';
import {
  buildPagination,
  buildSoftDeleteWhere,
} from '../../common/prisma/pagination';
import { AuditService } from '../audit/audit.service';
import { PrismaService } from '../prisma/prisma.service';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import { UserQueryDto } from './dto/user-query.dto';

const userListSelect = Prisma.validator<Prisma.UserSelect>()({
  id: true,
  employeeCode: true,
  name: true,
  email: true,
  roleId: true,
  departmentId: true,
  designation: true,
  managerId: true,
  status: true,
  lastLoginAt: true,
  createdAt: true,
  updatedAt: true,
  deletedAt: true,
  createdBy: true,
  updatedBy: true,
  deletedBy: true,
  role: {
    select: {
      id: true,
      name: true,
      slug: true,
      description: true,
      createdAt: true,
      updatedAt: true,
      deletedAt: true,
      createdBy: true,
      updatedBy: true,
      deletedBy: true,
    },
  },
  department: {
    select: {
      id: true,
      name: true,
      code: true,
      description: true,
      createdAt: true,
      updatedAt: true,
      deletedAt: true,
      createdBy: true,
      updatedBy: true,
      deletedBy: true,
    },
  },
  manager: {
    select: {
      id: true,
      employeeCode: true,
      name: true,
      email: true,
      designation: true,
      status: true,
      lastLoginAt: true,
      roleId: true,
      departmentId: true,
      managerId: true,
      createdAt: true,
      updatedAt: true,
      deletedAt: true,
      createdBy: true,
      updatedBy: true,
      deletedBy: true,
    },
  },
});

@Injectable()
export class UsersService {
  constructor(
    private readonly prisma: PrismaService,
    private readonly auditService: AuditService,
  ) {}

  async list(query: UserQueryDto) {
    const { skip, take, meta } = buildPagination(query);
    const where: Prisma.UserWhereInput = {
      ...buildSoftDeleteWhere(query.includeDeleted),
      ...(query.roleId ? { roleId: query.roleId } : {}),
      ...(query.departmentId ? { departmentId: query.departmentId } : {}),
      ...(query.managerId ? { managerId: query.managerId } : {}),
      ...(query.status ? { status: query.status } : {}),
      ...(query.search
        ? {
            OR: [
              { name: { contains: query.search } },
              { email: { contains: query.search } },
              { employeeCode: { contains: query.search } },
            ],
          }
        : {}),
    };

    const [items, total] = await Promise.all([
      this.prisma.user.findMany({
        where,
        skip,
        take,
        orderBy: { createdAt: query.sortOrder },
        select: userListSelect,
      }),
      this.prisma.user.count({ where }),
    ]);

    return { items, total, ...meta };
  }

  async create(dto: CreateUserDto, actor: JwtAuthUser) {
    await this.ensureRole(dto.roleId);
    await this.ensureDepartment(dto.departmentId);
    await this.ensureManager(dto.managerId);

    const existing = await this.prisma.user.findFirst({
      where: {
        OR: [{ email: dto.email }, { employeeCode: dto.employeeCode }],
      },
    });
    if (existing && !existing.deletedAt) {
      throw new ConflictException('User already exists');
    }

    const passwordHash = await hash(dto.password, 10);
    const user = await this.prisma.user.create({
      data: {
        employeeCode: dto.employeeCode,
        name: dto.name,
        email: dto.email,
        passwordHash,
        roleId: dto.roleId,
        departmentId: dto.departmentId,
        designation: dto.designation,
        managerId: dto.managerId,
        status: dto.status || UserStatus.ACTIVE,
        createdBy: actor.sub,
        updatedBy: actor.sub,
      },
      select: userListSelect,
    });

    await this.auditService.log({
      module: 'users',
      action: AuditAction.CREATE,
      entityType: AuditEntityType.USER,
      entityId: user.id,
      newValues: {
        employeeCode: dto.employeeCode,
        name: dto.name,
        email: dto.email,
        roleId: dto.roleId,
        departmentId: dto.departmentId,
        designation: dto.designation,
        managerId: dto.managerId,
        status: dto.status,
      } as Prisma.InputJsonValue,
      createdBy: actor.sub,
      updatedBy: actor.sub,
    });

    return user;
  }

  async findOne(id: string) {
    const user = await this.prisma.user.findUnique({
      where: { id },
      select: userListSelect,
    });
    if (!user) {
      throw new NotFoundException('User not found');
    }
    return user;
  }

  async update(id: string, dto: UpdateUserDto, actor: JwtAuthUser) {
    const existing = await this.findOne(id);
    if (dto.roleId) await this.ensureRole(dto.roleId);
    if (dto.departmentId) await this.ensureDepartment(dto.departmentId);
    await this.ensureManager(dto.managerId, id);

    const data: Prisma.UserUpdateInput = {
      employeeCode: dto.employeeCode,
      name: dto.name,
      email: dto.email,
      designation: dto.designation,
      status: dto.status,
      updatedBy: actor.sub,
      ...(dto.roleId ? { role: { connect: { id: dto.roleId } } } : {}),
      ...(dto.departmentId
        ? { department: { connect: { id: dto.departmentId } } }
        : {}),
      ...(dto.managerId !== undefined
        ? dto.managerId
          ? { manager: { connect: { id: dto.managerId } } }
          : { manager: { disconnect: true } }
        : {}),
      ...(dto.password ? { passwordHash: await hash(dto.password, 10) } : {}),
    };

    const updated = await this.prisma.user.update({
      where: { id },
      data,
      select: userListSelect,
    });

    await this.auditService.log({
      module: 'users',
      action: AuditAction.UPDATE,
      entityType: AuditEntityType.USER,
      entityId: id,
      oldValues: {
        employeeCode: existing.employeeCode,
        name: existing.name,
        email: existing.email,
        roleId: existing.roleId,
        departmentId: existing.departmentId,
        designation: existing.designation,
        managerId: existing.managerId,
        status: existing.status,
      } as Prisma.InputJsonValue,
      newValues: {
        ...dto,
        password: dto.password ? '***' : undefined,
      } as Prisma.InputJsonValue,
      createdBy: actor.sub,
      updatedBy: actor.sub,
    });

    return updated;
  }

  async remove(id: string, actor: JwtAuthUser) {
    await this.findOne(id);
    const deleted = await this.prisma.user.update({
      where: { id },
      data: {
        deletedAt: new Date(),
        deletedBy: actor.sub,
        updatedBy: actor.sub,
      },
    });

    await this.auditService.log({
      module: 'users',
      action: AuditAction.DELETE,
      entityType: AuditEntityType.USER,
      entityId: id,
      remarks: 'User soft deleted',
      createdBy: actor.sub,
      updatedBy: actor.sub,
      deletedBy: actor.sub,
    });

    return deleted;
  }

  async restore(id: string, actor: JwtAuthUser) {
    await this.findOne(id);
    const restored = await this.prisma.user.update({
      where: { id },
      data: {
        deletedAt: null,
        deletedBy: null,
        updatedBy: actor.sub,
      },
    });

    await this.auditService.log({
      module: 'users',
      action: AuditAction.RESTORE,
      entityType: AuditEntityType.USER,
      entityId: id,
      remarks: 'User restored',
      createdBy: actor.sub,
      updatedBy: actor.sub,
    });

    return restored;
  }

  private async ensureRole(id: string) {
    const role = await this.prisma.role.findFirst({
      where: { id, deletedAt: null },
    });
    if (!role) {
      throw new NotFoundException('Role not found');
    }
  }

  private async ensureDepartment(id: string) {
    const department = await this.prisma.department.findFirst({
      where: { id, deletedAt: null },
    });
    if (!department) {
      throw new NotFoundException('Department not found');
    }
  }

  private async ensureManager(managerId?: string, userId?: string) {
    if (!managerId) return;
    if (managerId === userId) {
      throw new ConflictException('User cannot be their own manager');
    }
    const manager = await this.prisma.user.findFirst({
      where: {
        id: managerId,
        deletedAt: null,
        status: UserStatus.ACTIVE,
      },
    });
    if (!manager) {
      throw new NotFoundException('Manager not found');
    }
  }
}
