import { describe, expect, it, vi } from 'vitest';
import type { AgentRuntimeProvider, RuntimeScope } from '@mosaicstack/types';
import {
  MatrixNativeRuntimeProvider,
  type MatrixRuntimeTransport,
} from './matrix-native-runtime-provider.js';
import {
  TmuxFleetRuntimeProvider,
  type FleetRuntimeTransport,
} from './tmux-fleet-runtime-provider.js';

const scope: RuntimeScope = {
  actorId: 'operator-1',
  tenantId: 'tenant-a',
  channelId: 'cli',
  correlationId: 'corr-1',
};

interface ProviderFixture {
  name: string;
  provider: AgentRuntimeProvider;
  providerId: string;
  verifySession: unknown;
  send: unknown;
}

function fixtures(): ProviderFixture[] {
  const fleetTransport: FleetRuntimeTransport = {
    verifySession: vi.fn(async () => ({
      id: 'session-1',
      runtimeId: 'native-1',
      socketName: 'fleet',
    })),
    listSessions: vi.fn(async () => [
      { id: 'session-1', runtimeId: 'native-1', socketName: 'fleet' },
    ]),
    sendMessage: vi.fn(async () => undefined),
    terminate: vi.fn(async () => undefined),
  };
  const fleet = new TmuxFleetRuntimeProvider({
    transport: fleetTransport,
    readAuthority: { canRead: vi.fn(async () => true) },
    writeAuthority: {
      canWrite: vi.fn(async () => true),
      assertAuthorized: vi.fn(async () => undefined),
    },
    attachmentIdFactory: () => 'attachment-1',
    now: () => new Date('2026-07-13T00:00:00.000Z'),
  });

  const matrixTransport: MatrixRuntimeTransport = {
    health: vi.fn(async () => ({
      status: 'healthy' as const,
      checkedAt: '2026-07-13T00:00:00.000Z',
    })),
    verifySession: vi.fn(async () => ({
      id: 'session-1',
      runtimeId: 'native-1',
      state: 'active' as const,
      createdAt: '2026-07-13T00:00:00.000Z',
      updatedAt: '2026-07-13T00:00:00.000Z',
    })),
    listSessions: vi.fn(async () => [
      {
        id: 'session-1',
        runtimeId: 'native-1',
        state: 'active' as const,
        createdAt: '2026-07-13T00:00:00.000Z',
        updatedAt: '2026-07-13T00:00:00.000Z',
      },
    ]),
    stream: async function* () {},
    send: vi.fn(async () => undefined),
    terminate: vi.fn(async () => undefined),
  };
  const matrix = new MatrixNativeRuntimeProvider({
    transport: matrixTransport,
    readAuthority: { canRead: vi.fn(async () => true) },
    writeAuthority: {
      canWrite: vi.fn(async () => true),
      assertAuthorized: vi.fn(async () => undefined),
    },
    attachmentIdFactory: () => 'attachment-1',
    now: () => new Date('2026-07-13T00:00:00.000Z'),
  });

  return [
    {
      name: 'tmux/fleet',
      provider: fleet,
      providerId: 'fleet.tmux',
      verifySession: fleetTransport.verifySession,
      send: fleetTransport.sendMessage,
    },
    {
      name: 'Matrix/native',
      provider: matrix,
      providerId: 'runtime.matrix',
      verifySession: matrixTransport.verifySession,
      send: matrixTransport.send,
    },
  ];
}

/** Shared contract tests for the migration-safe provider intersection. */
describe('tmux/fleet and Matrix/native provider parity', (): void => {
  it.each(fixtures())(
    '%s exposes the shared runtime operations',
    async (fixture): Promise<void> => {
      await expect(fixture.provider.capabilities(scope)).resolves.toEqual(
        expect.objectContaining({
          supported: expect.arrayContaining([
            'session.list',
            'session.tree',
            'session.send',
            'session.attach',
            'session.terminate',
          ]),
        }),
      );
      await expect(fixture.provider.listSessions(scope)).resolves.toEqual([
        expect.objectContaining({
          id: 'session-1',
          providerId: fixture.providerId,
          runtimeId: 'native-1',
        }),
      ]);
    },
  );

  it.each(fixtures())(
    '%s rejects empty messages before touching its transport',
    async (fixture): Promise<void> => {
      await expect(
        fixture.provider.sendMessage(
          'session-1',
          { content: '', idempotencyKey: 'message-1' },
          scope,
        ),
      ).rejects.toMatchObject({ code: 'invalid_request' });
      expect(fixture.verifySession).not.toHaveBeenCalled();
      expect(fixture.send).not.toHaveBeenCalled();
    },
  );

  it.each(fixtures())(
    '%s rejects an empty termination approval before touching its transport',
    async (fixture): Promise<void> => {
      await expect(fixture.provider.terminate('session-1', '', scope)).rejects.toMatchObject({
        code: 'invalid_request',
      });
      expect(fixture.verifySession).not.toHaveBeenCalled();
    },
  );

  it.each(fixtures())(
    '%s creates a read-only handle bound to immutable scope',
    async (fixture): Promise<void> => {
      await expect(fixture.provider.attach('session-1', 'control', scope)).rejects.toMatchObject({
        code: 'forbidden',
      });
      expect(fixture.verifySession).not.toHaveBeenCalled();

      await expect(fixture.provider.attach('session-1', 'read', scope)).resolves.toEqual(
        expect.objectContaining({
          attachmentId: 'attachment-1',
          sessionId: 'session-1',
          mode: 'read',
        }),
      );
      await expect(
        fixture.provider.detach('attachment-1', { ...scope, actorId: 'operator-2' }),
      ).rejects.toMatchObject({ code: 'forbidden' });
    },
  );
});
