import { Injectable } from '@nestjs/common';
import { Prisma } from '@prisma/client';
import { PrismaService } from '../../../shared/prisma/prisma.service';
import { AuditViewerScope, buildAuditListScopeWhere } from './audit-list-scope';

@Injectable()
export class AuditService {
  constructor(private prisma: PrismaService) {}

  async log(data: {
    operatorId?: bigint;
    operatorType: string;
    action: string;
    module: string;
    targetType?: string;
    targetId?: string;
    beforeData?: unknown;
    afterData?: unknown;
    ipAddress?: string;
  }) {
    return this.prisma.auditLog.create({
      data: {
        operatorId: data.operatorId,
        operatorType: data.operatorType,
        action: data.action,
        module: data.module,
        targetType: data.targetType,
        targetId: data.targetId,
        beforeData: data.beforeData ? JSON.stringify(data.beforeData) : null,
        afterData: data.afterData ? JSON.stringify(data.afterData) : null,
        ipAddress: data.ipAddress,
      },
    });
  }

  async list(
    page = 1,
    pageSize = 10,
    filters?: { module?: string; excludeModule?: string },
    viewer?: AuditViewerScope,
  ) {
    const skip = (page - 1) * pageSize;
    const scopeWhere = viewer ? await buildAuditListScopeWhere(this.prisma, viewer) : undefined;
    const where: Prisma.AuditLogWhereInput = {
      ...(filters?.module ? { module: filters.module } : {}),
      ...(filters?.excludeModule ? { module: { not: filters.excludeModule } } : {}),
      ...(scopeWhere ?? {}),
    };
    const [rows, total] = await Promise.all([
      this.prisma.auditLog.findMany({
        where,
        orderBy: { createdAt: 'desc' },
        skip,
        take: pageSize,
      }),
      this.prisma.auditLog.count({ where }),
    ]);

    const operatorIds = [
      ...new Set(rows.map((r) => r.operatorId).filter((id): id is bigint => id != null)),
    ];
    const operators =
      operatorIds.length > 0
        ? await this.prisma.user.findMany({
            where: { id: { in: operatorIds } },
            select: {
              id: true,
              username: true,
              userType: true,
              adminRole: { select: { role: { select: { code: true } } } },
            },
          })
        : [];
    const operatorById = new Map(operators.map((u) => [u.id.toString(), u]));

    const items = rows.map((row) => {
      const op = row.operatorId ? operatorById.get(row.operatorId.toString()) : null;
      return {
        id: row.id.toString(),
        action: row.action,
        module: row.module,
        targetType: row.targetType,
        targetId: row.targetId,
        operatorId: row.operatorId?.toString() ?? null,
        operatorUsername: op?.username ?? null,
        operatorRole: op?.adminRole?.role?.code ?? null,
        operatorUserType: op?.userType ?? null,
        operatorType: row.operatorType,
        ipAddress: row.ipAddress,
        createdAt: row.createdAt,
      };
    });

    return { items, total, page, pageSize };
  }
}
