import { beforeEach, describe, expect, it, vi } from "vitest";
import { NotFoundException } from "@nestjs/common";
import { EncryptionService } from "../../security/encryption.service";
import { AgentProvidersService } from "./agent-providers.service";
import { PrismaService } from "../../prisma/prisma.service";

describe("AgentProvidersService", () => {
  let service: AgentProvidersService;
  let prisma: {
    agentProviderConfig: {
      findMany: ReturnType<typeof vi.fn>;
      findUnique: ReturnType<typeof vi.fn>;
      create: ReturnType<typeof vi.fn>;
      update: ReturnType<typeof vi.fn>;
      delete: ReturnType<typeof vi.fn>;
    };
  };
  let encryptionService: {
    encryptIfNeeded: ReturnType<typeof vi.fn>;
  };

  beforeEach(() => {
    prisma = {
      agentProviderConfig: {
        findMany: vi.fn(),
        findUnique: vi.fn(),
        create: vi.fn(),
        update: vi.fn(),
        delete: vi.fn(),
      },
    };

    encryptionService = {
      encryptIfNeeded: vi.fn((value: string) => `enc:${value}`),
    };

    service = new AgentProvidersService(
      prisma as unknown as PrismaService,
      encryptionService as unknown as EncryptionService
    );
  });

  it("lists all provider configs", async () => {
    const expected = [
      {
        id: "cfg-1",
        workspaceId: "8bcd7eda-a122-4d6c-adfd-b152f6f75369",
        name: "Primary",
        provider: "openai",
        gatewayUrl: "https://gateway.example.com",
        credentials: {},
        isActive: true,
        createdAt: new Date("2026-03-07T18:00:00.000Z"),
        updatedAt: new Date("2026-03-07T18:00:00.000Z"),
      },
    ];
    prisma.agentProviderConfig.findMany.mockResolvedValue(expected);

    const result = await service.list();

    expect(prisma.agentProviderConfig.findMany).toHaveBeenCalledWith({
      orderBy: [{ createdAt: "desc" }, { id: "desc" }],
    });
    expect(result).toEqual(expected);
  });

  it("returns a single provider config", async () => {
    const expected = {
      id: "cfg-1",
      workspaceId: "8bcd7eda-a122-4d6c-adfd-b152f6f75369",
      name: "Primary",
      provider: "openai",
      gatewayUrl: "https://gateway.example.com",
      credentials: { apiKeyRef: "vault:openai" },
      isActive: true,
      createdAt: new Date("2026-03-07T18:00:00.000Z"),
      updatedAt: new Date("2026-03-07T18:00:00.000Z"),
    };
    prisma.agentProviderConfig.findUnique.mockResolvedValue(expected);

    const result = await service.getById("cfg-1");

    expect(prisma.agentProviderConfig.findUnique).toHaveBeenCalledWith({
      where: { id: "cfg-1" },
    });
    expect(result).toEqual(expected);
  });

  it("throws NotFoundException when provider config is missing", async () => {
    prisma.agentProviderConfig.findUnique.mockResolvedValue(null);

    await expect(service.getById("missing")).rejects.toBeInstanceOf(NotFoundException);
  });

  it("creates a provider config with default credentials", async () => {
    const created = {
      id: "cfg-created",
      workspaceId: "8bcd7eda-a122-4d6c-adfd-b152f6f75369",
      name: "New Provider",
      provider: "claude",
      gatewayUrl: "https://gateway.example.com",
      credentials: {},
      isActive: true,
      createdAt: new Date("2026-03-07T18:00:00.000Z"),
      updatedAt: new Date("2026-03-07T18:00:00.000Z"),
    };
    prisma.agentProviderConfig.create.mockResolvedValue(created);

    const result = await service.create({
      workspaceId: "8bcd7eda-a122-4d6c-adfd-b152f6f75369",
      name: "New Provider",
      provider: "claude",
      gatewayUrl: "https://gateway.example.com",
    });

    expect(prisma.agentProviderConfig.create).toHaveBeenCalledWith({
      data: {
        workspaceId: "8bcd7eda-a122-4d6c-adfd-b152f6f75369",
        name: "New Provider",
        provider: "claude",
        gatewayUrl: "https://gateway.example.com",
        credentials: {},
      },
    });
    expect(encryptionService.encryptIfNeeded).not.toHaveBeenCalled();
    expect(result).toEqual(created);
  });

  it("encrypts openclaw token credentials when creating provider config", async () => {
    const created = {
      id: "cfg-openclaw",
      workspaceId: "8bcd7eda-a122-4d6c-adfd-b152f6f75369",
      name: "OpenClaw",
      provider: "openclaw",
      gatewayUrl: "https://openclaw.example.com",
      credentials: { apiToken: "enc:top-secret" },
      isActive: true,
      createdAt: new Date("2026-03-07T18:00:00.000Z"),
      updatedAt: new Date("2026-03-07T18:00:00.000Z"),
    };
    prisma.agentProviderConfig.create.mockResolvedValue(created);

    const result = await service.create({
      workspaceId: "8bcd7eda-a122-4d6c-adfd-b152f6f75369",
      name: "OpenClaw",
      provider: "openclaw",
      gatewayUrl: "https://openclaw.example.com",
      credentials: { apiToken: "top-secret" },
    });

    expect(encryptionService.encryptIfNeeded).toHaveBeenCalledWith("top-secret");
    expect(prisma.agentProviderConfig.create).toHaveBeenCalledWith({
      data: {
        workspaceId: "8bcd7eda-a122-4d6c-adfd-b152f6f75369",
        name: "OpenClaw",
        provider: "openclaw",
        gatewayUrl: "https://openclaw.example.com",
        credentials: { apiToken: "enc:top-secret" },
      },
    });
    expect(result).toEqual(created);
  });

  it("updates a provider config", async () => {
    prisma.agentProviderConfig.findUnique.mockResolvedValue({
      id: "cfg-1",
      workspaceId: "8bcd7eda-a122-4d6c-adfd-b152f6f75369",
      name: "Primary",
      provider: "openai",
      gatewayUrl: "https://gateway.example.com",
      credentials: {},
      isActive: true,
      createdAt: new Date("2026-03-07T18:00:00.000Z"),
      updatedAt: new Date("2026-03-07T18:00:00.000Z"),
    });

    const updated = {
      id: "cfg-1",
      workspaceId: "8bcd7eda-a122-4d6c-adfd-b152f6f75369",
      name: "Secondary",
      provider: "openai",
      gatewayUrl: "https://gateway2.example.com",
      credentials: { apiKeyRef: "vault:new" },
      isActive: false,
      createdAt: new Date("2026-03-07T18:00:00.000Z"),
      updatedAt: new Date("2026-03-07T19:00:00.000Z"),
    };
    prisma.agentProviderConfig.update.mockResolvedValue(updated);

    const result = await service.update("cfg-1", {
      name: "Secondary",
      gatewayUrl: "https://gateway2.example.com",
      credentials: { apiKeyRef: "vault:new" },
      isActive: false,
    });

    expect(prisma.agentProviderConfig.update).toHaveBeenCalledWith({
      where: { id: "cfg-1" },
      data: {
        name: "Secondary",
        gatewayUrl: "https://gateway2.example.com",
        credentials: { apiKeyRef: "vault:new" },
        isActive: false,
      },
    });
    expect(encryptionService.encryptIfNeeded).not.toHaveBeenCalled();
    expect(result).toEqual(updated);
  });

  it("encrypts openclaw token credentials when updating provider config", async () => {
    prisma.agentProviderConfig.findUnique.mockResolvedValue({
      id: "cfg-openclaw",
      workspaceId: "8bcd7eda-a122-4d6c-adfd-b152f6f75369",
      name: "OpenClaw",
      provider: "openclaw",
      gatewayUrl: "https://openclaw.example.com",
      credentials: { apiToken: "enc:existing" },
      isActive: true,
      createdAt: new Date("2026-03-07T18:00:00.000Z"),
      updatedAt: new Date("2026-03-07T18:00:00.000Z"),
    });

    const updated = {
      id: "cfg-openclaw",
      workspaceId: "8bcd7eda-a122-4d6c-adfd-b152f6f75369",
      name: "OpenClaw",
      provider: "openclaw",
      gatewayUrl: "https://openclaw.example.com",
      credentials: { apiToken: "enc:rotated-token" },
      isActive: true,
      createdAt: new Date("2026-03-07T18:00:00.000Z"),
      updatedAt: new Date("2026-03-07T19:00:00.000Z"),
    };
    prisma.agentProviderConfig.update.mockResolvedValue(updated);

    const result = await service.update("cfg-openclaw", {
      credentials: { apiToken: "rotated-token" },
    });

    expect(encryptionService.encryptIfNeeded).toHaveBeenCalledWith("rotated-token");
    expect(prisma.agentProviderConfig.update).toHaveBeenCalledWith({
      where: { id: "cfg-openclaw" },
      data: {
        credentials: { apiToken: "enc:rotated-token" },
      },
    });
    expect(result).toEqual(updated);
  });

  it("throws NotFoundException when updating a missing provider config", async () => {
    prisma.agentProviderConfig.findUnique.mockResolvedValue(null);

    await expect(service.update("missing", { name: "Updated" })).rejects.toBeInstanceOf(
      NotFoundException
    );
    expect(prisma.agentProviderConfig.update).not.toHaveBeenCalled();
  });

  it("deletes a provider config", async () => {
    prisma.agentProviderConfig.findUnique.mockResolvedValue({
      id: "cfg-1",
      workspaceId: "8bcd7eda-a122-4d6c-adfd-b152f6f75369",
      name: "Primary",
      provider: "openai",
      gatewayUrl: "https://gateway.example.com",
      credentials: {},
      isActive: true,
      createdAt: new Date("2026-03-07T18:00:00.000Z"),
      updatedAt: new Date("2026-03-07T18:00:00.000Z"),
    });

    const deleted = {
      id: "cfg-1",
      workspaceId: "8bcd7eda-a122-4d6c-adfd-b152f6f75369",
      name: "Primary",
      provider: "openai",
      gatewayUrl: "https://gateway.example.com",
      credentials: {},
      isActive: true,
      createdAt: new Date("2026-03-07T18:00:00.000Z"),
      updatedAt: new Date("2026-03-07T18:00:00.000Z"),
    };
    prisma.agentProviderConfig.delete.mockResolvedValue(deleted);

    const result = await service.delete("cfg-1");

    expect(prisma.agentProviderConfig.delete).toHaveBeenCalledWith({
      where: { id: "cfg-1" },
    });
    expect(result).toEqual(deleted);
  });

  it("throws NotFoundException when deleting a missing provider config", async () => {
    prisma.agentProviderConfig.findUnique.mockResolvedValue(null);

    await expect(service.delete("missing")).rejects.toBeInstanceOf(NotFoundException);
    expect(prisma.agentProviderConfig.delete).not.toHaveBeenCalled();
  });
});
