import { afterEach, describe, expect, it, vi } from 'vitest';
import { InMemoryDurableSessionStore } from '@mosaicstack/agent';
import {
  createDiscordIngressEnvelope,
  DiscordPlugin,
  type DiscordIngressPayload,
} from '@mosaicstack/discord-plugin';
import { InteractionController } from '../../agent/interaction.controller.js';
import { RuntimeProviderService } from '../../agent/runtime-provider-registry.service.js';
import { DurableSessionService } from '../../agent/durable-session.service.js';
import { ChatGateway } from '../../chat/chat.gateway.js';
import { CommandAuthorizationService } from '../../commands/command-authorization.service.js';

const SERVICE_TOKEN = 'test-discord-service-token';
const envKeys = [
  'DISCORD_SERVICE_TOKEN',
  'DISCORD_SERVICE_TENANT_ID',
  'DISCORD_INTERACTION_BINDINGS',
  'DISCORD_ALLOWED_GUILD_IDS',
  'DISCORD_ALLOWED_CHANNEL_IDS',
  'DISCORD_ALLOWED_USER_IDS',
  'MOSAIC_AGENT_NAME',
] as const;
const priorEnv = new Map<string, string | undefined>();

function payload(content: string, messageId: string, correlationId: string): DiscordIngressPayload {
  return {
    content,
    messageId,
    correlationId,
    guildId: 'guild-1',
    channelId: 'channel-1',
    userId: 'discord-admin-1',
    conversationId: 'Nova:discord:channel-1',
  };
}

function authorization(): CommandAuthorizationService {
  const entries = new Map<string, string>();
  return new CommandAuthorizationService(
    {
      select: () => ({
        from: () => ({ where: () => ({ limit: async () => [{ role: 'admin' }] }) }),
      }),
    } as never,
    {
      get: async (key: string) => entries.get(key) ?? null,
      set: async (key: string, value: string) => entries.set(key, value),
      del: async (key: string) => Number(entries.delete(key)),
    },
  );
}

describe('interaction Discord/CLI durable-session integration', () => {
  afterEach(() => {
    for (const key of envKeys) {
      const value = priorEnv.get(key);
      if (value === undefined) delete process.env[key];
      else process.env[key] = value;
    }
    priorEnv.clear();
  });

  it('enrolls through the CLI surface then resolves the same durable session from Discord', async () => {
    for (const key of envKeys) priorEnv.set(key, process.env[key]);
    process.env['MOSAIC_AGENT_NAME'] = 'Nova';
    process.env['DISCORD_SERVICE_TOKEN'] = SERVICE_TOKEN;
    process.env['DISCORD_SERVICE_TENANT_ID'] = 'tenant-1';
    process.env['DISCORD_ALLOWED_GUILD_IDS'] = 'guild-1';
    process.env['DISCORD_ALLOWED_CHANNEL_IDS'] = 'channel-1';
    process.env['DISCORD_ALLOWED_USER_IDS'] = 'discord-admin-1';
    process.env['DISCORD_INTERACTION_BINDINGS'] = JSON.stringify([
      {
        instanceId: 'Nova',
        guildId: 'guild-1',
        channelId: 'channel-1',
        pairedUsers: {
          'discord-admin-1': { role: 'admin', mosaicUserId: 'mosaic-admin-1' },
        },
      },
    ]);

    const durable = new DurableSessionService(
      new InMemoryDurableSessionStore() as never,
      {} as never,
    );
    const enrollmentRuntime = {
      listSessions: vi.fn().mockResolvedValue([{ id: 'runtime-1' }]),
    };
    const controller = new InteractionController(enrollmentRuntime as never, durable);
    await controller.enroll(
      'Nova',
      'Nova:discord:channel-1',
      { providerId: 'fleet', runtimeSessionId: 'runtime-1' },
      { id: 'mosaic-admin-1', tenantId: 'tenant-1' },
      'cli-enrollment-correlation',
    );

    const authz = authorization();
    const terminated = vi.fn().mockResolvedValue(undefined);
    const runtime = new RuntimeProviderService(
      {
        require: () => ({
          capabilities: async () => ({ supported: ['session.terminate'] }),
          terminate: terminated,
        }),
      } as never,
      { record: async () => undefined } as never,
      {
        consume: (approvalId, action) =>
          authz.consumeRuntimeTerminationApproval(approvalId, action),
      },
    );
    const gateway = new ChatGateway(
      {} as never,
      {} as never,
      {} as never,
      {} as never,
      {} as never,
      {} as never,
      authz,
      runtime,
      durable,
    );
    const client = { data: { discordService: true }, emit: vi.fn() };
    const plugin = new DiscordPlugin({
      token: 'unused',
      gatewayUrl: 'http://unused',
      serviceToken: SERVICE_TOKEN,
      allowedGuildIds: ['guild-1'],
      allowedChannelIds: ['channel-1'],
      allowedUserIds: ['discord-admin-1'],
      interactionBindings: [
        {
          instanceId: 'Nova',
          guildId: 'guild-1',
          channelId: 'channel-1',
          pairedUsers: {
            'discord-admin-1': { role: 'admin', mosaicUserId: 'mosaic-admin-1' },
          },
        },
      ],
    });
    const pluginInternals = plugin as unknown as {
      client: { user: { id: string } };
      socket: { connected: boolean; emit: ReturnType<typeof vi.fn> };
      handleDiscordMessage(message: unknown): void;
    };
    const pluginSocket = { connected: true, emit: vi.fn() };
    pluginInternals.client = { user: { id: 'bot-1' } };
    pluginInternals.socket = pluginSocket;
    pluginInternals.handleDiscordMessage({
      id: 'approve-1',
      guildId: 'guild-1',
      channelId: 'channel-1',
      author: { id: 'discord-admin-1', bot: false },
      mentions: { has: () => true },
      content: '<@bot-1> /approve',
      channel: { parentId: null },
      attachments: new Map(),
    });

    expect(pluginSocket.emit).toHaveBeenCalledWith('discord:approve', expect.any(Object));
    const approvalEnvelope = pluginSocket.emit.mock.calls[0]?.[1];
    await gateway.handleDiscordApproval(client as never, approvalEnvelope);
    const approval = client.emit.mock.calls.find(
      ([event]) => event === 'discord:approval',
    )?.[1] as {
      approvalId: string;
      success: boolean;
    };
    expect(approval.success).toBe(true);

    await gateway.handleDiscordStop(
      client as never,
      createDiscordIngressEnvelope(
        payload(`/stop ${approval.approvalId}`, 'stop-1', 'discord-stop-correlation'),
        SERVICE_TOKEN,
      ),
    );

    expect(terminated).toHaveBeenCalledWith(
      'runtime-1',
      approval.approvalId,
      expect.objectContaining({ actorId: 'mosaic-admin-1' }),
    );
    expect(client.emit).toHaveBeenCalledWith('discord:stop', {
      correlationId: 'discord-stop-correlation',
      success: true,
    });
  });
});
