import { Injectable, Inject } from "@nestjs/common";
import { Prisma } from "@prisma/client";
import { PrismaService } from "../database/prisma.service";
import { getErrorMessage } from "@bff/core/utils/error.util";
import { Logger } from "nestjs-pino";

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;
  action: AuditAction;
  resource?: string;
  details?: Record<string, unknown> | string | number | boolean | null;
  ipAddress?: string;
  userAgent?: string;
  success?: boolean;
  error?: string;
}

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

  async log(data: AuditLogData): Promise<void> {
    try {
      await this.prisma.auditLog.create({
        data: {
          userId: data.userId,
          action: data.action,
          resource: data.resource,
          details:
            data.details === undefined
              ? undefined
              : data.details === null
                ? Prisma.JsonNull
                : (JSON.parse(JSON.stringify(data.details)) as Prisma.InputJsonValue),
          ipAddress: data.ipAddress,
          userAgent: data.userAgent,
          success: data.success ?? true,
          error: data.error,
        },
      });
    } catch (error) {
      this.logger.error("Audit logging failed", {
        errorType: error instanceof Error ? error.constructor.name : "Unknown",
        message: getErrorMessage(error),
      });
    }
  }

  async logAuthEvent(
    action: AuditAction,
    userId?: string,
    details?: Record<string, unknown> | string | number | boolean | null,
    request?: {
      headers?: Record<string, string | string[] | undefined>;
      ip?: string;
      connection?: { remoteAddress?: string };
      socket?: { remoteAddress?: string };
    },
    success: boolean = true,
    error?: string
  ): Promise<void> {
    const ipAddress = this.extractIpAddress(request);
    const uaHeader = request?.headers?.["user-agent"];
    const userAgent = Array.isArray(uaHeader) ? uaHeader[0] : uaHeader;

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

  private extractIpAddress(request?: {
    headers?: Record<string, string | string[] | undefined>;
    connection?: { remoteAddress?: string };
    socket?: { remoteAddress?: string };
    ip?: string;
  }): string | undefined {
    if (!request) return undefined;

    return (
      (typeof request.headers?.["x-forwarded-for"] === "string"
        ? request.headers["x-forwarded-for"].split(",")[0]?.trim()
        : Array.isArray(request.headers?.["x-forwarded-for"]) &&
            request.headers?.["x-forwarded-for"][0]
          ? request.headers["x-forwarded-for"][0]
          : undefined) ||
      (typeof request.headers?.["x-real-ip"] === "string"
        ? request.headers["x-real-ip"]
        : undefined) ||
      request.connection?.remoteAddress ||
      request.socket?.remoteAddress ||
      request.ip
    );
  }

  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,
    };
  }
}
