import { describe, expect, it, vi } from 'vitest';
import type { RuntimeScope, RuntimeStreamEvent } from '@mosaicstack/types';
import {
  type MatrixRuntimeSession,
  type MatrixRuntimeTransport,
  MatrixNativeRuntimeProvider,
  type MatrixReadAuthority,
  type MatrixWriteAuthority,
} from './matrix-native-runtime-provider.js';

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

const session: MatrixRuntimeSession = {
  id: 'native-1',
  runtimeId: '@worker:example.test',
  state: 'active',
  createdAt: '2026-07-13T00:00:00.000Z',
  updatedAt: '2026-07-13T00:00:00.000Z',
};

function transport(): MatrixRuntimeTransport {
  return {
    health: vi.fn(async () => ({
      status: 'healthy' as const,
      checkedAt: '2026-07-13T00:00:00.000Z',
    })),
    listSessions: vi.fn(async () => [session]),
    verifySession: vi.fn(async (sessionId) => {
      if (sessionId !== session.id) throw new Error('unexpected session');
      return session;
    }),
    stream: vi.fn(async function* (): AsyncIterable<RuntimeStreamEvent> {
      yield {
        type: 'message.delta',
        sessionId: 'native-1',
        cursor: 'cursor-1',
        occurredAt: '2026-07-13T00:00:00.000Z',
        content: 'hello',
      };
    }),
    send: vi.fn(async () => undefined),
    terminate: vi.fn(async () => undefined),
  };
}

function readAuthority(): MatrixReadAuthority {
  return { canRead: vi.fn(async () => true) };
}

function writeAuthority(): MatrixWriteAuthority {
  return {
    canWrite: vi.fn(async () => true),
    assertAuthorized: vi.fn(async () => undefined),
  };
}

describe('MatrixNativeRuntimeProvider contract boundary', (): void => {
  it('advertises the concrete Matrix operations and returns only normalized sessions', async (): Promise<void> => {
    const provider = new MatrixNativeRuntimeProvider({
      transport: transport(),
      readAuthority: readAuthority(),
    });

    await expect(provider.capabilities(scope)).resolves.toEqual({
      supported: [
        'session.list',
        'session.tree',
        'session.stream',
        'session.send',
        'session.attach',
        'session.terminate',
      ],
    });
    await expect(provider.listSessions(scope)).resolves.toEqual([
      expect.objectContaining({ id: 'native-1', providerId: 'runtime.matrix' }),
    ]);
  });

  it('binds read attachments to immutable scope and rejects control mode before transport access', async (): Promise<void> => {
    const matrix = transport();
    const provider = new MatrixNativeRuntimeProvider({
      transport: matrix,
      readAuthority: readAuthority(),
      attachmentIdFactory: () => 'attachment-1',
      now: () => new Date('2026-07-13T00:00:00.000Z'),
    });

    await expect(provider.attach('native-1', 'control', scope)).rejects.toMatchObject({
      code: 'forbidden',
    });
    expect(matrix.verifySession).not.toHaveBeenCalled();

    await provider.attach('native-1', 'read', scope);
    await expect(
      provider.detach('attachment-1', { ...scope, tenantId: 'other' }),
    ).rejects.toMatchObject({
      code: 'forbidden',
    });
  });

  it('validates messages and applies exact Matrix authority after bound-session verification', async (): Promise<void> => {
    const matrix = transport();
    const writes = writeAuthority();
    const provider = new MatrixNativeRuntimeProvider({
      transport: matrix,
      readAuthority: readAuthority(),
      writeAuthority: writes,
    });

    await expect(
      provider.sendMessage('native-1', { content: '', idempotencyKey: 'msg-1' }, scope),
    ).rejects.toMatchObject({
      code: 'invalid_request',
    });
    expect(matrix.verifySession).not.toHaveBeenCalled();

    await provider.sendMessage('native-1', { content: 'hello', idempotencyKey: 'msg-1' }, scope);
    expect(writes.assertAuthorized).toHaveBeenCalledWith({
      operation: 'session.send',
      sessionId: 'native-1',
      scope,
    });
    expect(matrix.send).toHaveBeenCalledWith(
      'native-1',
      { content: 'hello', idempotencyKey: 'msg-1' },
      scope,
    );
  });

  it('streams only after exact read authorization', async (): Promise<void> => {
    const matrix = transport();
    const provider = new MatrixNativeRuntimeProvider({
      transport: matrix,
      readAuthority: readAuthority(),
    });

    await expect(collect(provider.streamSession('native-1', 'cursor-0', scope))).resolves.toEqual([
      expect.objectContaining({ type: 'message.delta', sessionId: 'native-1' }),
    ]);
    expect(matrix.stream).toHaveBeenCalledWith('native-1', 'cursor-0', scope);
  });
});

async function collect(stream: AsyncIterable<RuntimeStreamEvent>): Promise<RuntimeStreamEvent[]> {
  const values: RuntimeStreamEvent[] = [];
  for await (const value of stream) values.push(value);
  return values;
}
