import { Injectable, Inject } from "@nestjs/common";
import { Prisma } from "@prisma/client";
import { PrismaService } from "../database/prisma.service.js";
import { extractErrorMessage } from "@bff/core/utils/error.util.js";
import { Logger } from "nestjs-pino";
import { extractClientIp, extractUserAgent } from "@bff/core/http/request-context.util.js";

export enum AuditAction {
  LOGIN_SUCCESS = "LOGIN_SUCCESS",
  LOGIN_FAILED = "LOGIN_FAILED",
  LOGOUT = "LOGOUT",
  SIGNUP = "SIGNUP",
  PASSWORD_RESET = "PASSWORD_RESET",
  PASSWORD_CHANGE = "PASSWORD_CHANGE",
  ACCOUNT_LOCKED = "ACCOUNT_LOCKED",
  ACCOUNT_UNLOCKED = "ACCOUNT_UNLOCKED",
  PROFILE_UPDATE = "PROFILE_UPDATE",
  MFA_ENABLED = "MFA_ENABLED",
  MFA_DISABLED = "MFA_DISABLED",
  API_ACCESS = "API_ACCESS",
  SYSTEM_MAINTENANCE = "SYSTEM_MAINTENANCE",
}

export interface AuditLogData {
  userId?: string | undefined;
  action: AuditAction;
  resource?: string | undefined;
  details?: Record<string, unknown> | string | number | boolean | null | undefined;
  ipAddress?: string | undefined;
  userAgent?: string | undefined;
  success?: boolean | undefined;
  error?: string | undefined;
}

/**
 * Minimal request shape for audit logging.
 * Compatible with Express Request but only requires the fields needed for IP/UA extraction.
 * Must be compatible with RequestContextLike from request-context.util.ts.
 */
export type AuditRequest = {
  headers?: Record<string, string | string[] | undefined> | undefined;
  ip?: string | undefined;
  connection?: { remoteAddress?: string | undefined } | undefined;
  socket?: { remoteAddress?: string | undefined } | undefined;
};

@Injectable()
export class AuditService {
  constructor(
    private readonly prisma: PrismaService,
    @Inject(Logger) private readonly logger: Logger
  ) {}

  async log(data: AuditLogData): Promise<void> {
    try {
      const createData: Parameters<typeof this.prisma.auditLog.create>[0]["data"] = {
        action: data.action,
        success: data.success ?? true,
      };

      if (data.userId !== undefined) createData.userId = data.userId;
      if (data.resource !== undefined) createData.resource = data.resource;
      if (data.ipAddress !== undefined) createData.ipAddress = data.ipAddress;
      if (data.userAgent !== undefined) createData.userAgent = data.userAgent;
      if (data.error !== undefined) createData.error = data.error;
      if (data.details !== undefined) {
        createData.details =
          data.details === null
            ? Prisma.JsonNull
            : (JSON.parse(JSON.stringify(data.details)) as Prisma.InputJsonValue);
      }

      await this.prisma.auditLog.create({ data: createData });
    } catch (error) {
      this.logger.error("Audit logging failed", {
        errorType: error instanceof Error ? error.constructor.name : "Unknown",
        message: extractErrorMessage(error),
      });
    }
  }

  async logAuthEvent(
    action: AuditAction,
    userId?: string,
    details?: Record<string, unknown> | string | number | boolean | null,
    request?: AuditRequest,
    success: boolean = true,
    error?: string
  ): Promise<void> {
    const ipAddress = extractClientIp(request);
    const userAgent = extractUserAgent(request);

    await this.log({
      userId,
      action,
      resource: "auth",
      details,
      ipAddress,
      userAgent,
      success,
      error,
    });
  }

  async getAuditLogs({
    page,
    limit,
    action,
    userId,
  }: {
    page: number;
    limit: number;
    action?: AuditAction;
    userId?: string;
  }) {
    const skip = (page - 1) * limit;
    const where: Prisma.AuditLogWhereInput = {};
    if (action) where.action = action;
    if (userId) where.userId = userId;

    const [logs, total] = await Promise.all([
      this.prisma.auditLog.findMany({
        where,
        include: {
          user: {
            select: {
              id: true,
              email: true,
            },
          },
        },
        orderBy: { createdAt: "desc" },
        skip,
        take: limit,
      }),
      this.prisma.auditLog.count({ where }),
    ]);

    return { logs, total };
  }

  async getSecurityStats() {
    const today = new Date(new Date().setHours(0, 0, 0, 0));

    const [totalUsers, lockedAccounts, failedLoginsToday, successfulLoginsToday] =
      await Promise.all([
        this.prisma.user.count(),
        this.prisma.user.count({
          where: {
            lockedUntil: {
              gt: new Date(),
            },
          },
        }),
        this.prisma.auditLog.count({
          where: {
            action: AuditAction.LOGIN_FAILED,
            createdAt: {
              gte: today,
            },
          },
        }),
        this.prisma.auditLog.count({
          where: {
            action: AuditAction.LOGIN_SUCCESS,
            createdAt: {
              gte: today,
            },
          },
        }),
      ]);

    return {
      totalUsers,
      lockedAccounts,
      failedLoginsToday,
      successfulLoginsToday,
      securityEventsToday: failedLoginsToday + successfulLoginsToday,
    };
  }
}
