import type { HttpService } from "@nestjs/axios";
import type {
  AgentMessage,
  AgentSession,
  AgentSessionList,
  IAgentProvider,
  InjectResult,
} from "@mosaic/shared";
import type { AgentProviderConfig } from "@prisma/client";
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { InternalAgentProvider } from "../../src/api/agents/internal-agent.provider";
import { AgentProviderRegistry } from "../../src/api/agents/agent-provider.registry";
import { MissionControlController } from "../../src/api/mission-control/mission-control.controller";
import { MissionControlService } from "../../src/api/mission-control/mission-control.service";
import { OpenClawProviderFactory } from "../../src/api/providers/openclaw/openclaw.provider-factory";
import { OpenClawSseBridge } from "../../src/api/providers/openclaw/openclaw-sse.bridge";
import { ProvidersModule } from "../../src/api/providers/providers.module";
import type { PrismaService } from "../../src/prisma/prisma.service";
import type { EncryptionService } from "../../src/security/encryption.service";

type MockProvider = IAgentProvider & {
  listSessions: ReturnType<typeof vi.fn>;
  getSession: ReturnType<typeof vi.fn>;
  injectMessage: ReturnType<typeof vi.fn>;
  pauseSession: ReturnType<typeof vi.fn>;
  killSession: ReturnType<typeof vi.fn>;
};

type MockPrisma = {
  agentProviderConfig: {
    create: ReturnType<typeof vi.fn>;
    findMany: ReturnType<typeof vi.fn>;
  };
  operatorAuditLog: {
    create: ReturnType<typeof vi.fn>;
  };
};

const emptyMessageStream = async function* (): AsyncIterable<AgentMessage> {
  return;
};

describe("MS23-P3-004 API integration", () => {
  let controller: MissionControlController;
  let providersModule: ProvidersModule;
  let registry: AgentProviderRegistry;
  let prisma: MockPrisma;
  let httpService: {
    axiosRef: {
      get: ReturnType<typeof vi.fn>;
      post: ReturnType<typeof vi.fn>;
    };
  };

  const gatewayUrl = "https://openclaw-gateway.example.com";
  const internalSession: AgentSession = {
    id: "session-internal-1",
    providerId: "internal",
    providerType: "internal",
    status: "active",
    createdAt: new Date("2026-03-07T16:00:00.000Z"),
    updatedAt: new Date("2026-03-07T16:02:00.000Z"),
  };

  const openClawGatewaySession = {
    id: "session-openclaw-1",
    status: "running",
    createdAt: "2026-03-07T16:01:00.000Z",
    updatedAt: "2026-03-07T16:03:00.000Z",
  };

  const createInternalProvider = (session: AgentSession): MockProvider => ({
    providerId: "internal",
    providerType: "internal",
    displayName: "Internal",
    listSessions: vi.fn().mockResolvedValue({ sessions: [session], total: 1 } as AgentSessionList),
    getSession: vi.fn().mockImplementation(async (sessionId: string) => {
      return sessionId === session.id ? session : null;
    }),
    getMessages: vi.fn().mockResolvedValue([]),
    injectMessage: vi.fn().mockResolvedValue({ accepted: true } as InjectResult),
    pauseSession: vi.fn().mockResolvedValue(undefined),
    resumeSession: vi.fn().mockResolvedValue(undefined),
    killSession: vi.fn().mockResolvedValue(undefined),
    streamMessages: vi.fn().mockReturnValue(emptyMessageStream()),
    isAvailable: vi.fn().mockResolvedValue(true),
  });

  beforeEach(() => {
    const providerConfigs: AgentProviderConfig[] = [];

    prisma = {
      agentProviderConfig: {
        create: vi.fn().mockImplementation(async (args: { data: Record<string, unknown> }) => {
          const now = new Date("2026-03-07T15:00:00.000Z");
          const record: AgentProviderConfig = {
            id: `cfg-${String(providerConfigs.length + 1)}`,
            workspaceId: String(args.data.workspaceId),
            name: String(args.data.name),
            provider: String(args.data.provider),
            gatewayUrl: String(args.data.gatewayUrl),
            credentials: (args.data.credentials ?? {}) as AgentProviderConfig["credentials"],
            isActive: args.data.isActive !== false,
            createdAt: now,
            updatedAt: now,
          };

          providerConfigs.push(record);
          return record;
        }),
        findMany: vi
          .fn()
          .mockImplementation(
            async (args: { where?: { provider?: string; isActive?: boolean } }) => {
              const where = args.where ?? {};
              return providerConfigs.filter((config) => {
                if (where.provider !== undefined && config.provider !== where.provider) {
                  return false;
                }

                if (where.isActive !== undefined && config.isActive !== where.isActive) {
                  return false;
                }

                return true;
              });
            }
          ),
      },
      operatorAuditLog: {
        create: vi.fn().mockResolvedValue(undefined),
      },
    };

    httpService = {
      axiosRef: {
        get: vi.fn().mockImplementation(async (url: string) => {
          if (url === `${gatewayUrl}/api/sessions`) {
            return {
              data: {
                sessions: [openClawGatewaySession],
                total: 1,
              },
            };
          }

          if (url === `${gatewayUrl}/api/sessions/${openClawGatewaySession.id}`) {
            return {
              data: {
                session: openClawGatewaySession,
              },
            };
          }

          throw new Error(`Unexpected GET ${url}`);
        }),
        post: vi.fn().mockImplementation(async (url: string) => {
          if (url.endsWith("/inject")) {
            return { data: { accepted: true, messageId: "msg-inject-1" } };
          }

          if (url.endsWith("/pause") || url.endsWith("/kill")) {
            return { data: {} };
          }

          throw new Error(`Unexpected POST ${url}`);
        }),
      },
    };

    const internalProvider = createInternalProvider(internalSession);
    registry = new AgentProviderRegistry(internalProvider as unknown as InternalAgentProvider);
    registry.onModuleInit();

    const encryptionService = {
      decryptIfNeeded: vi.fn().mockReturnValue("plain-openclaw-token"),
    };

    const sseBridge = new OpenClawSseBridge(httpService as unknown as HttpService);
    const openClawProviderFactory = new OpenClawProviderFactory(
      encryptionService as unknown as EncryptionService,
      httpService as unknown as HttpService,
      sseBridge
    );

    providersModule = new ProvidersModule(
      prisma as unknown as PrismaService,
      registry,
      openClawProviderFactory
    );

    const missionControlService = new MissionControlService(
      registry,
      prisma as unknown as PrismaService
    );

    controller = new MissionControlController(missionControlService);
  });

  it("Phase 3 gate: OpenClaw provider config registered in DB → provider loaded on boot → sessions returned from /api/mission-control/sessions → inject/pause/kill proxied to gateway", async () => {
    await prisma.agentProviderConfig.create({
      data: {
        workspaceId: "workspace-ms23",
        name: "openclaw-home",
        provider: "openclaw",
        gatewayUrl,
        credentials: {
          apiToken: "enc:test-openclaw-token",
        },
        isActive: true,
      },
    });

    await providersModule.onModuleInit();

    // Equivalent to GET /api/mission-control/sessions
    const sessionsResponse = await controller.listSessions();

    expect(sessionsResponse.sessions.map((session) => session.id)).toEqual([
      "session-openclaw-1",
      "session-internal-1",
    ]);
    expect(sessionsResponse.sessions).toEqual(
      expect.arrayContaining([
        expect.objectContaining({
          id: "session-internal-1",
          providerId: "internal",
        }),
        expect.objectContaining({
          id: "session-openclaw-1",
          providerId: "openclaw-home",
          providerType: "openclaw",
        }),
      ])
    );

    const operatorRequest = {
      user: {
        id: "operator-ms23",
      },
    };

    await expect(
      controller.injectMessage(
        "session-openclaw-1",
        {
          message: "Ship it",
        },
        operatorRequest
      )
    ).resolves.toEqual({ accepted: true, messageId: "msg-inject-1" });

    await expect(controller.pauseSession("session-openclaw-1", operatorRequest)).resolves.toEqual({
      message: "Session session-openclaw-1 paused",
    });

    await expect(
      controller.killSession(
        "session-openclaw-1",
        {
          force: false,
        },
        operatorRequest
      )
    ).resolves.toEqual({ message: "Session session-openclaw-1 killed" });

    expect(httpService.axiosRef.post).toHaveBeenNthCalledWith(
      1,
      `${gatewayUrl}/api/sessions/session-openclaw-1/inject`,
      { content: "Ship it" },
      {
        headers: {
          Authorization: "Bearer plain-openclaw-token",
        },
      }
    );

    expect(httpService.axiosRef.post).toHaveBeenNthCalledWith(
      2,
      `${gatewayUrl}/api/sessions/session-openclaw-1/pause`,
      {},
      {
        headers: {
          Authorization: "Bearer plain-openclaw-token",
        },
      }
    );

    expect(httpService.axiosRef.post).toHaveBeenNthCalledWith(
      3,
      `${gatewayUrl}/api/sessions/session-openclaw-1/kill`,
      { force: false },
      {
        headers: {
          Authorization: "Bearer plain-openclaw-token",
        },
      }
    );

    expect(prisma.operatorAuditLog.create).toHaveBeenNthCalledWith(1, {
      data: {
        sessionId: "session-openclaw-1",
        userId: "operator-ms23",
        provider: "openclaw-home",
        action: "inject",
        content: "Ship it",
        metadata: {
          payload: {
            message: "Ship it",
          },
        },
      },
    });
  });
});
