import { beforeEach, describe, expect, it, vi } from "vitest";
import { hash } from "bcryptjs";
import { OnboardingService } from "./onboarding.service";
import { PrismaService } from "../prisma/prisma.service";
import { CryptoService } from "../crypto/crypto.service";

vi.mock("bcryptjs", () => ({
  hash: vi.fn(),
}));

describe("OnboardingService", () => {
  let service: OnboardingService;

  const mockPrismaService = {
    systemConfig: {
      findUnique: vi.fn(),
      upsert: vi.fn(),
    },
    breakglassUser: {
      count: vi.fn(),
      create: vi.fn(),
      findFirst: vi.fn(),
    },
    llmProvider: {
      create: vi.fn(),
    },
  };

  const mockCryptoService = {
    encrypt: vi.fn(),
  };

  beforeEach(() => {
    vi.clearAllMocks();

    service = new OnboardingService(
      mockPrismaService as unknown as PrismaService,
      mockCryptoService as unknown as CryptoService
    );
  });

  it("isCompleted returns false when no config exists", async () => {
    mockPrismaService.systemConfig.findUnique.mockResolvedValue(null);

    await expect(service.isCompleted()).resolves.toBe(false);
    expect(mockPrismaService.systemConfig.findUnique).toHaveBeenCalledWith({
      where: { key: "onboarding.completed" },
    });
  });

  it("isCompleted returns true when completed", async () => {
    mockPrismaService.systemConfig.findUnique.mockResolvedValue({
      id: "cfg-1",
      key: "onboarding.completed",
      value: "true",
      encrypted: false,
      updatedAt: new Date(),
    });

    await expect(service.isCompleted()).resolves.toBe(true);
  });

  it("createBreakglassUser hashes password and creates record", async () => {
    const mockedHash = vi.mocked(hash);
    mockedHash.mockResolvedValue("hashed-password");

    mockPrismaService.breakglassUser.count.mockResolvedValue(0);
    mockPrismaService.breakglassUser.create.mockResolvedValue({
      id: "breakglass-1",
      username: "admin",
    });

    const result = await service.createBreakglassUser("admin", "supersecret123");

    expect(mockedHash).toHaveBeenCalledWith("supersecret123", 12);
    expect(mockPrismaService.breakglassUser.create).toHaveBeenCalledWith({
      data: {
        username: "admin",
        passwordHash: "hashed-password",
      },
      select: {
        id: true,
        username: true,
      },
    });
    expect(result).toEqual({ id: "breakglass-1", username: "admin" });
  });

  it("createBreakglassUser rejects if user already exists", async () => {
    mockPrismaService.breakglassUser.count.mockResolvedValue(1);

    await expect(service.createBreakglassUser("admin", "supersecret123")).rejects.toThrow(
      "Breakglass user already exists"
    );
  });

  it("configureOidc encrypts secret and saves to SystemConfig", async () => {
    mockCryptoService.encrypt.mockReturnValue("enc:oidc-secret");
    mockPrismaService.systemConfig.upsert.mockResolvedValue({
      id: "cfg",
      key: "oidc.clientSecret",
      value: "enc:oidc-secret",
      encrypted: true,
      updatedAt: new Date(),
    });

    await service.configureOidc("https://auth.example.com", "client-id", "client-secret");

    expect(mockCryptoService.encrypt).toHaveBeenCalledWith("client-secret");
    expect(mockPrismaService.systemConfig.upsert).toHaveBeenCalledTimes(3);
    expect(mockPrismaService.systemConfig.upsert).toHaveBeenCalledWith({
      where: { key: "oidc.issuerUrl" },
      create: {
        key: "oidc.issuerUrl",
        value: "https://auth.example.com",
        encrypted: false,
      },
      update: {
        value: "https://auth.example.com",
        encrypted: false,
      },
    });
    expect(mockPrismaService.systemConfig.upsert).toHaveBeenCalledWith({
      where: { key: "oidc.clientId" },
      create: {
        key: "oidc.clientId",
        value: "client-id",
        encrypted: false,
      },
      update: {
        value: "client-id",
        encrypted: false,
      },
    });
    expect(mockPrismaService.systemConfig.upsert).toHaveBeenCalledWith({
      where: { key: "oidc.clientSecret" },
      create: {
        key: "oidc.clientSecret",
        value: "enc:oidc-secret",
        encrypted: true,
      },
      update: {
        value: "enc:oidc-secret",
        encrypted: true,
      },
    });
  });

  it("addProvider encrypts apiKey and creates LlmProvider", async () => {
    mockCryptoService.encrypt.mockReturnValue("enc:api-key");
    mockPrismaService.llmProvider.create.mockResolvedValue({
      id: "provider-1",
    });

    const result = await service.addProvider("breakglass-1", {
      name: "my-openai",
      displayName: "OpenAI",
      type: "openai",
      baseUrl: "https://api.openai.com/v1",
      apiKey: "sk-test",
      models: [{ id: "gpt-4o-mini", name: "GPT-4o Mini" }],
    });

    expect(mockCryptoService.encrypt).toHaveBeenCalledWith("sk-test");
    expect(mockPrismaService.llmProvider.create).toHaveBeenCalledWith({
      data: {
        userId: "breakglass-1",
        name: "my-openai",
        displayName: "OpenAI",
        type: "openai",
        baseUrl: "https://api.openai.com/v1",
        apiKey: "enc:api-key",
        models: [{ id: "gpt-4o-mini", name: "GPT-4o Mini" }],
      },
      select: {
        id: true,
      },
    });
    expect(result).toEqual({ id: "provider-1" });
  });

  it("complete sets SystemConfig flag", async () => {
    mockPrismaService.systemConfig.upsert.mockResolvedValue({
      id: "cfg-1",
      key: "onboarding.completed",
      value: "true",
      encrypted: false,
      updatedAt: new Date(),
    });

    await service.complete();

    expect(mockPrismaService.systemConfig.upsert).toHaveBeenCalledWith({
      where: { key: "onboarding.completed" },
      create: {
        key: "onboarding.completed",
        value: "true",
        encrypted: false,
      },
      update: {
        value: "true",
        encrypted: false,
      },
    });
  });
});
