import {
  ConflictException,
  Injectable,
  NotFoundException,
} from '@nestjs/common';
import { AuditAction, AuditEntityType } from '@aechr/shared';
import { Prisma } from '@prisma/client';
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 { CreateRoleDto } from './dto/create-role.dto';
import { RoleQueryDto } from './dto/role-query.dto';
import { UpdateRolePermissionsDto } from './dto/update-role-permissions.dto';
import { UpdateRoleDto } from './dto/update-role.dto';

const roleListSelect = Prisma.validator<Prisma.RoleSelect>()({
  id: true,
  name: true,
  slug: true,
  description: true,
  createdAt: true,
  updatedAt: true,
  deletedAt: true,
  createdBy: true,
  updatedBy: true,
  deletedBy: true,
  rolePermissions: {
    where: { deletedAt: null, permission: { deletedAt: null } },
    select: {
      id: true,
      permissionId: true,
    },
  },
});

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

  async list(query: RoleQueryDto) {
    const { skip, take, meta } = buildPagination(query);
    const where: Prisma.RoleWhereInput = {
      ...buildSoftDeleteWhere(query.includeDeleted),
      ...(query.slug ? { slug: { contains: query.slug } } : {}),
      ...(query.search
        ? {
            OR: [
              { name: { contains: query.search } },
              { slug: { contains: query.search } },
            ],
          }
        : {}),
    };

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

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

  async create(dto: CreateRoleDto, actor: JwtAuthUser) {
    const existing = await this.prisma.role.findFirst({
      where: {
        OR: [{ name: dto.name }, { slug: dto.slug }],
      },
    });
    if (existing && !existing.deletedAt) {
      throw new ConflictException('Role already exists');
    }

    const role = await this.prisma.role.create({
      data: {
        name: dto.name,
        slug: dto.slug,
        description: dto.description,
        createdBy: actor.sub,
        updatedBy: actor.sub,
      },
    });

    await this.auditService.log({
      module: 'roles',
      action: AuditAction.CREATE,
      entityType: AuditEntityType.ROLE,
      entityId: role.id,
      newValues: dto as unknown as Prisma.InputJsonValue,
      createdBy: actor.sub,
      updatedBy: actor.sub,
    });

    return role;
  }

  async findOne(id: string) {
    const role = await this.prisma.role.findUnique({
      where: { id },
      include: {
        rolePermissions: {
          include: { permission: true },
        },
      },
    });
    if (!role) {
      throw new NotFoundException('Role not found');
    }
    return role;
  }

  async update(id: string, dto: UpdateRoleDto, actor: JwtAuthUser) {
    const role = await this.ensureRole(id);

    const updated = await this.prisma.role.update({
      where: { id },
      data: {
        ...dto,
        updatedBy: actor.sub,
      },
    });

    await this.auditService.log({
      module: 'roles',
      action: AuditAction.UPDATE,
      entityType: AuditEntityType.ROLE,
      entityId: id,
      oldValues: {
        name: role.name,
        slug: role.slug,
        description: role.description,
      } as Prisma.InputJsonValue,
      newValues: dto as Prisma.InputJsonValue,
      createdBy: actor.sub,
      updatedBy: actor.sub,
    });

    return updated;
  }

  async remove(id: string, actor: JwtAuthUser) {
    await this.ensureRole(id);

    const deleted = await this.prisma.role.update({
      where: { id },
      data: {
        deletedAt: new Date(),
        deletedBy: actor.sub,
        updatedBy: actor.sub,
      },
    });

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

    return deleted;
  }

  async restore(id: string, actor: JwtAuthUser) {
    await this.ensureRole(id);

    const restored = await this.prisma.role.update({
      where: { id },
      data: {
        deletedAt: null,
        deletedBy: null,
        updatedBy: actor.sub,
      },
    });

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

    return restored;
  }

  async getPermissions(id: string) {
    await this.ensureRole(id);

    return this.prisma.rolePermission.findMany({
      where: {
        roleId: id,
        deletedAt: null,
        permission: { deletedAt: null },
      },
      include: { permission: true },
      orderBy: { createdAt: 'asc' },
    });
  }

  async updatePermissions(
    id: string,
    dto: UpdateRolePermissionsDto,
    actor: JwtAuthUser,
  ) {
    await this.ensureRole(id);

    const permissions = await this.prisma.permission.findMany({
      where: {
        id: { in: dto.permissionIds },
        deletedAt: null,
      },
    });

    if (permissions.length !== dto.permissionIds.length) {
      throw new NotFoundException('One or more permissions were not found');
    }

    await this.prisma.$transaction(async (tx) => {
      await tx.rolePermission.updateMany({
        where: {
          roleId: id,
          deletedAt: null,
          permissionId: { notIn: dto.permissionIds },
        },
        data: {
          deletedAt: new Date(),
          deletedBy: actor.sub,
          updatedBy: actor.sub,
        },
      });

      for (const permissionId of dto.permissionIds) {
        const existing = await tx.rolePermission.findFirst({
          where: { roleId: id, permissionId },
        });

        if (!existing) {
          await tx.rolePermission.create({
            data: {
              roleId: id,
              permissionId,
              createdBy: actor.sub,
              updatedBy: actor.sub,
            },
          });
          continue;
        }

        if (existing.deletedAt) {
          await tx.rolePermission.update({
            where: { id: existing.id },
            data: {
              deletedAt: null,
              deletedBy: null,
              updatedBy: actor.sub,
            },
          });
        }
      }
    });

    await this.auditService.log({
      module: 'roles',
      action: AuditAction.UPDATE,
      entityType: AuditEntityType.ROLE,
      entityId: id,
      newValues: { permissionIds: dto.permissionIds } as Prisma.InputJsonValue,
      remarks: 'Role permissions updated',
      createdBy: actor.sub,
      updatedBy: actor.sub,
    });

    return this.getPermissions(id);
  }

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