import { Logger } from "@nestjs/common";
import type { AgentProviderConfig } from "@prisma/client";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { PrismaService } from "../../prisma/prisma.service";
import { AgentProviderRegistry } from "../agents/agent-provider.registry";
import { OpenClawProviderFactory } from "./openclaw/openclaw.provider-factory";
import { ProvidersModule } from "./providers.module";

type MockOpenClawProvider = {
  providerId: string;
  validateBaseUrl: ReturnType<typeof vi.fn>;
  validateToken: ReturnType<typeof vi.fn>;
  isAvailable: ReturnType<typeof vi.fn>;
};

describe("ProvidersModule", () => {
  let moduleRef: ProvidersModule;
  let prisma: {
    agentProviderConfig: {
      findMany: ReturnType<typeof vi.fn>;
    };
  };
  let registry: {
    registerProvider: ReturnType<typeof vi.fn>;
  };
  let factory: {
    createProvider: ReturnType<typeof vi.fn>;
  };

  const config: AgentProviderConfig = {
    id: "cfg-openclaw-1",
    workspaceId: "workspace-1",
    name: "openclaw-home",
    provider: "openclaw",
    gatewayUrl: "https://gateway.example.com",
    credentials: { apiToken: "enc:token-value" },
    isActive: true,
    createdAt: new Date("2026-03-07T15:00:00.000Z"),
    updatedAt: new Date("2026-03-07T15:00:00.000Z"),
  };

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

    registry = {
      registerProvider: vi.fn(),
    };

    factory = {
      createProvider: vi.fn(),
    };

    moduleRef = new ProvidersModule(
      prisma as unknown as PrismaService,
      registry as unknown as AgentProviderRegistry,
      factory as unknown as OpenClawProviderFactory
    );
  });

  it("registers reachable OpenClaw providers", async () => {
    const provider: MockOpenClawProvider = {
      providerId: "openclaw-home",
      validateBaseUrl: vi.fn(),
      validateToken: vi.fn(),
      isAvailable: vi.fn().mockResolvedValue(true),
    };

    prisma.agentProviderConfig.findMany.mockResolvedValue([config]);
    factory.createProvider.mockReturnValue(provider);

    await moduleRef.onModuleInit();

    expect(prisma.agentProviderConfig.findMany).toHaveBeenCalledWith({
      where: {
        provider: "openclaw",
        isActive: true,
      },
      orderBy: [{ createdAt: "asc" }, { id: "asc" }],
    });
    expect(factory.createProvider).toHaveBeenCalledWith(config);
    expect(provider.validateBaseUrl).toHaveBeenCalledTimes(1);
    expect(provider.validateToken).toHaveBeenCalledTimes(1);
    expect(provider.isAvailable).toHaveBeenCalledTimes(1);
    expect(registry.registerProvider).toHaveBeenCalledWith(provider);
  });

  it("skips provider registration when gateway is unreachable", async () => {
    const warnSpy = vi.spyOn(Logger.prototype, "warn").mockImplementation(() => undefined);
    const provider: MockOpenClawProvider = {
      providerId: "openclaw-home",
      validateBaseUrl: vi.fn(),
      validateToken: vi.fn(),
      isAvailable: vi.fn().mockResolvedValue(false),
    };

    prisma.agentProviderConfig.findMany.mockResolvedValue([config]);
    factory.createProvider.mockReturnValue(provider);

    await moduleRef.onModuleInit();

    expect(registry.registerProvider).not.toHaveBeenCalled();
    expect(warnSpy).toHaveBeenCalledWith(
      expect.stringContaining("Skipping OpenClaw provider openclaw-home")
    );
  });

  it("skips provider registration when token decryption fails", async () => {
    const errorSpy = vi.spyOn(Logger.prototype, "error").mockImplementation(() => undefined);
    const provider: MockOpenClawProvider = {
      providerId: "openclaw-home",
      validateBaseUrl: vi.fn(),
      validateToken: vi.fn().mockImplementation(() => {
        throw new Error("Failed to decrypt API token");
      }),
      isAvailable: vi.fn().mockResolvedValue(true),
    };

    prisma.agentProviderConfig.findMany.mockResolvedValue([config]);
    factory.createProvider.mockReturnValue(provider);

    await moduleRef.onModuleInit();

    expect(registry.registerProvider).not.toHaveBeenCalled();
    expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining("token decryption failed"));
    expect(provider.isAvailable).not.toHaveBeenCalled();
  });
});
