import { type Type } from '@nestjs/common';
import { Test, type TestingModule } from '@nestjs/testing';
import type { SlashCommandPayload } from '@mosaicstack/types';
import { describe, expect, it, vi } from 'vitest';
import { AgentService, type AgentSession } from '../agent/agent.service.js';
import { ProviderService } from '../agent/provider.service.js';
import { AppModule } from '../app.module.js';
import { CommandAuthorizationService } from '../commands/command-authorization.service.js';
import { CommandExecutorService } from '../commands/command-executor.service.js';
import { CommandsModule } from '../commands/commands.module.js';
import { CommandRuntimeApprovalVerifier } from '../commands/runtime-approval-verifier.js';
import { PreferencesModule } from '../preferences/preferences.module.js';
import { SystemOverrideService } from '../preferences/system-override.service.js';

const fakeDb = {
  $client: { exec: async (): Promise<void> => {} },
  execute: async (): Promise<{ rows: unknown[] }> => ({ rows: [] }),
  select: () => ({
    from: () => ({
      where: async (): Promise<Array<{ count: number }>> => [{ count: 1 }],
    }),
  }),
  insert: () => ({ values: async (): Promise<void> => {} }),
};

const fakeProviderService = {
  onModuleInit: async (): Promise<void> => {},
  onModuleDestroy: (): void => {},
  getRegistry: () => ({ getAvailable: () => [], getAll: () => [], find: () => undefined }),
  getDefaultModel: () => undefined,
  listAvailableModels: () => [],
  listProviders: () => [],
  getAdapter: () => undefined,
  getProvidersHealth: () => [],
};

function compileRealAppGraph(): Promise<TestingModule> {
  return Test.createTestingModule({ imports: [AppModule] })
    .overrideProvider('DB_HANDLE')
    .useValue({ db: fakeDb, close: async (): Promise<void> => {} })
    .overrideProvider('DB')
    .useValue(fakeDb)
    .overrideProvider('STORAGE_ADAPTER')
    .useValue({
      name: 'required-security-wiring-test',
      migrate: async (): Promise<void> => {},
      close: async (): Promise<void> => {},
    })
    .overrideProvider('AUTH')
    .useValue({})
    .overrideProvider('BRAIN')
    .useValue({ conversations: {}, agents: {} })
    .overrideProvider('LOG_SERVICE')
    .useValue({})
    .overrideProvider('MEMORY')
    .useValue({})
    .overrideProvider('MEMORY_ADAPTER')
    .useValue({})
    .overrideProvider(ProviderService)
    .useValue(fakeProviderService)
    .compile();
}

function providerToken(provider: unknown): unknown {
  return typeof provider === 'function' ? provider : (provider as { provide?: unknown })?.provide;
}

interface MaskingConsumer {
  moduleType: Type<unknown>;
  token: Type<unknown>;
  useValue: object;
}

async function compileWithoutProvider(
  moduleType: Type<unknown>,
  missingToken: Type<unknown>,
  maskingConsumer: MaskingConsumer,
): Promise<{ error: unknown; moduleRef: TestingModule | undefined }> {
  const touchedModules = new Set([moduleType, maskingConsumer.moduleType]);
  const originals = Array.from(touchedModules, (touchedModule: Type<unknown>) => ({
    moduleType: touchedModule,
    providers: (Reflect.getMetadata('providers', touchedModule) ?? []) as unknown[],
    exports: (Reflect.getMetadata('exports', touchedModule) ?? []) as unknown[],
  }));

  for (const original of originals) {
    const providers = original.providers.flatMap((provider: unknown): unknown[] => {
      const token = providerToken(provider);
      if (original.moduleType === moduleType && token === missingToken) return [];
      if (original.moduleType === maskingConsumer.moduleType && token === maskingConsumer.token) {
        return [{ provide: maskingConsumer.token, useValue: maskingConsumer.useValue }];
      }
      return [provider];
    });
    const exports = original.exports.filter(
      (exported: unknown): boolean =>
        original.moduleType !== moduleType || providerToken(exported) !== missingToken,
    );
    Reflect.defineMetadata('providers', providers, original.moduleType);
    Reflect.defineMetadata('exports', exports, original.moduleType);
  }

  let moduleRef: TestingModule | undefined;
  let error: unknown;
  try {
    moduleRef = await compileRealAppGraph();
  } catch (caught: unknown) {
    error = caught;
  } finally {
    for (const original of originals) {
      Reflect.defineMetadata('providers', original.providers, original.moduleType);
      Reflect.defineMetadata('exports', original.exports, original.moduleType);
    }
  }
  return { error, moduleRef };
}

async function closeIfCompiled(moduleRef: TestingModule | undefined): Promise<void> {
  if (moduleRef) await moduleRef.close();
}

describe('required security wiring — real AppModule startup refusal', () => {
  it('FL-01 positive control: the real graph compiles when CommandAuthorizationService is bound', async () => {
    const moduleRef = await compileRealAppGraph();
    try {
      expect(moduleRef.get(CommandAuthorizationService, { strict: false })).toBeInstanceOf(
        CommandAuthorizationService,
      );
    } finally {
      await moduleRef.close();
    }
  });

  it('FL-01 negative control: absence read as permission is refused at module compilation', async () => {
    const { error, moduleRef } = await compileWithoutProvider(
      CommandsModule,
      CommandAuthorizationService,
      {
        moduleType: CommandsModule,
        token: CommandRuntimeApprovalVerifier,
        useValue: {},
      },
    );
    await closeIfCompiled(moduleRef);

    expect(
      error,
      'absence read as permission: AppModule compilation accepted a missing CommandAuthorizationService binding',
    ).toBeInstanceOf(Error);
    if (!(error instanceof Error)) return;
    expect(error.message).toContain('CommandExecutorService');
    expect(error.message).toContain('CommandAuthorizationService');
  });

  it('FL-11 positive control: the real graph compiles when SystemOverrideService is bound', async () => {
    const moduleRef = await compileRealAppGraph();
    try {
      expect(moduleRef.get(SystemOverrideService, { strict: false })).toBeInstanceOf(
        SystemOverrideService,
      );
    } finally {
      await moduleRef.close();
    }
  });

  it('FL-11 negative control: absence read as permission is refused at module compilation', async () => {
    const { error, moduleRef } = await compileWithoutProvider(
      PreferencesModule,
      SystemOverrideService,
      {
        moduleType: CommandsModule,
        token: CommandExecutorService,
        useValue: {},
      },
    );
    await closeIfCompiled(moduleRef);

    expect(
      error,
      'absence read as permission: AppModule compilation accepted a missing SystemOverrideService binding',
    ).toBeInstanceOf(Error);
    if (!(error instanceof Error)) return;
    expect(error.message).toContain('AgentService');
    expect(error.message).toContain('SystemOverrideService');
  });
});

const actorScope = { userId: 'security-user', tenantId: 'security-tenant' };
const conversationId = 'security-conversation';

function directExecutorWithoutAuthorization(systemOverrideSet: ReturnType<typeof vi.fn>) {
  const registry = {
    getManifest: vi.fn(() => ({
      version: 1,
      commands: [
        {
          name: 'system',
          aliases: [],
          description: 'Set instruction authority',
          scope: 'agent' as const,
          execution: 'socket' as const,
          available: true,
        },
      ],
      skills: [],
    })),
  };
  return new CommandExecutorService(
    registry as never,
    { getSession: vi.fn() } as never,
    { set: systemOverrideSet, clear: vi.fn() } as never,
    { collect: vi.fn() } as never,
    null,
    { agents: {} } as never,
    null,
    null,
    { getServerStatuses: vi.fn(() => []), getToolDefinitions: vi.fn(() => []) } as never,
    undefined as never,
  );
}

function directAgentWithoutSystemOverride(piPrompt: ReturnType<typeof vi.fn>): {
  service: AgentService;
  session: AgentSession;
} {
  const service = new AgentService(
    {
      getDefaultModel: vi.fn(() => null),
      getRegistry: vi.fn(() => ({})),
      findModel: vi.fn(),
      listAvailableModels: vi.fn(() => []),
    } as never,
    {} as never,
    {} as never,
    { available: false } as never,
    {} as never,
    { getToolDefinitions: vi.fn(() => []) } as never,
    { loadForSession: vi.fn(async () => ({ metaTools: [], promptAdditions: [] })) } as never,
    undefined as never,
    null,
    { collect: vi.fn().mockResolvedValue(undefined) } as never,
    null,
  );
  const session = {
    id: conversationId,
    provider: 'test-provider',
    modelId: 'test-model',
    piSession: { prompt: piPrompt },
    listeners: new Set(),
    unsubscribe: vi.fn(),
    createdAt: Date.now(),
    promptCount: 0,
    channels: new Set(),
    skillPromptAdditions: [],
    sandboxDir: process.cwd(),
    allowedTools: null,
    userId: actorScope.userId,
    tenantId: actorScope.tenantId,
    metrics: {
      tokens: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
      modelSwitches: 0,
      messageCount: 0,
      lastActivityAt: new Date(0).toISOString(),
    },
  } as unknown as AgentSession;
  const internals = service as unknown as { sessions: Map<string, AgentSession> };
  internals.sessions.set(conversationId, session);
  return { service, session };
}

describe('required security wiring — malformed direct absence has zero effects', () => {
  it('FL-01 refuses command execution before any command effect when authorization is absent', async () => {
    const systemOverrideSet = vi.fn().mockResolvedValue(undefined);
    const executor = directExecutorWithoutAuthorization(systemOverrideSet);
    const payload: SlashCommandPayload = {
      command: 'system',
      args: 'authority that must not be stored',
      conversationId,
    };
    let error: unknown;

    try {
      await executor.execute(payload, actorScope);
    } catch (caught: unknown) {
      error = caught;
    }

    expect
      .soft(
        error,
        'absence read as permission: direct executor accepted missing command authorization',
      )
      .toBeInstanceOf(Error);
    expect
      .soft(
        systemOverrideSet,
        'absence read as permission: command effect occurred without command authorization',
      )
      .not.toHaveBeenCalled();
  });

  it('FL-11 refuses prompt execution before any provider or session effect when system override authority is absent', async () => {
    const piPrompt = vi.fn().mockResolvedValue(undefined);
    const { service, session } = directAgentWithoutSystemOverride(piPrompt);
    let error: unknown;

    try {
      await service.prompt(conversationId, 'must not reach provider', actorScope);
    } catch (caught: unknown) {
      error = caught;
    }

    expect
      .soft(
        error,
        'absence read as permission: direct session accepted missing system override authority',
      )
      .toBeInstanceOf(Error);
    expect
      .soft(
        piPrompt,
        'absence read as permission: provider prompt occurred without system override authority',
      )
      .not.toHaveBeenCalled();
    expect
      .soft(
        session.promptCount,
        'absence read as permission: session state changed without system override authority',
      )
      .toBe(0);
  });
});
