import { describe, expect, it, vi } from 'vitest';
import type { RuntimeScope } from '@mosaicstack/types';
import {
  type FleetReadAuthority,
  type FleetWriteAuthority,
  TmuxFleetRuntimeProvider,
} from './tmux-fleet-runtime-provider.js';
import type {
  FleetRuntimeProviderError,
  FleetRuntimeTarget,
  FleetRuntimeTransport,
} from './tmux-fleet-runtime-provider.js';

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

const target: FleetRuntimeTarget = {
  id: 'coder0',
  runtimeId: 'codex',
  socketName: 'tess-fleet',
};

function transport(): FleetRuntimeTransport {
  return {
    verifySession: vi.fn(async (): Promise<FleetRuntimeTarget> => target),
    listSessions: vi.fn(async (): Promise<FleetRuntimeTarget[]> => [target]),
    sendMessage: vi.fn(async (): Promise<void> => undefined),
    terminate: vi.fn(async (): Promise<void> => undefined),
  };
}

function readAuthority(): FleetReadAuthority {
  return { canRead: vi.fn(async (): Promise<boolean> => true) };
}

describe('TmuxFleetRuntimeProvider security policy', (): void => {
  it('advertises only fleet operations it can safely implement', async (): Promise<void> => {
    const provider = new TmuxFleetRuntimeProvider({ transport: transport() });

    await expect(provider.capabilities(scope)).resolves.toEqual({
      supported: [
        'session.list',
        'session.tree',
        'session.send',
        'session.attach',
        'session.terminate',
      ],
    });
  });

  it('rejects control attach without consulting the tmux transport', async (): Promise<void> => {
    const fleet = transport();
    const provider = new TmuxFleetRuntimeProvider({ transport: fleet });

    await expect(provider.attach('coder0', 'control', scope)).rejects.toMatchObject({
      code: 'forbidden',
    } satisfies Partial<FleetRuntimeProviderError>);
    expect(fleet.verifySession).not.toHaveBeenCalled();
  });

  it('denies fleet listing without an exact-scope read authority decision', async (): Promise<void> => {
    const fleet = transport();
    const provider = new TmuxFleetRuntimeProvider({ transport: fleet });

    await expect(provider.listSessions(scope)).rejects.toMatchObject({
      code: 'forbidden',
    } satisfies Partial<FleetRuntimeProviderError>);
    expect(fleet.listSessions).not.toHaveBeenCalled();
  });

  it('denies read attachment without an exact-scope read authority decision', async (): Promise<void> => {
    const fleet = transport();
    const provider = new TmuxFleetRuntimeProvider({ transport: fleet });

    await expect(provider.attach('coder0', 'read', scope)).rejects.toMatchObject({
      code: 'forbidden',
    } satisfies Partial<FleetRuntimeProviderError>);
    expect(fleet.verifySession).not.toHaveBeenCalled();
  });

  it('creates a read-only attachment only after exact target verification', async (): Promise<void> => {
    const fleet = transport();
    const provider = new TmuxFleetRuntimeProvider({
      transport: fleet,
      readAuthority: readAuthority(),
      attachmentIdFactory: (): string => 'attachment-1',
      now: (): Date => new Date('2026-07-12T00:00:00.000Z'),
    });

    await expect(provider.attach('coder0', 'read', scope)).resolves.toEqual({
      attachmentId: 'attachment-1',
      sessionId: 'coder0',
      mode: 'read',
      expiresAt: '2026-07-12T00:05:00.000Z',
    });
    expect(fleet.verifySession).toHaveBeenCalledWith('coder0');
  });

  it('denies attachment-handle replay from another immutable actor scope', async (): Promise<void> => {
    const fleet = transport();
    const provider = new TmuxFleetRuntimeProvider({
      transport: fleet,
      readAuthority: readAuthority(),
      attachmentIdFactory: (): string => 'attachment-1',
      now: (): Date => new Date('2026-07-12T00:00:00.000Z'),
    });
    await provider.attach('coder0', 'read', scope);

    await expect(
      provider.detach('attachment-1', { ...scope, actorId: 'operator-2' }),
    ).rejects.toMatchObject({
      code: 'forbidden',
    } satisfies Partial<FleetRuntimeProviderError>);
  });

  it('keeps attachment scope immutable after caller-side scope mutation', async (): Promise<void> => {
    const mutableScope = { ...scope };
    const provider = new TmuxFleetRuntimeProvider({
      transport: transport(),
      readAuthority: readAuthority(),
      attachmentIdFactory: (): string => 'attachment-1',
      now: (): Date => new Date('2026-07-12T00:00:00.000Z'),
    });
    await provider.attach('coder0', 'read', mutableScope);
    mutableScope.actorId = 'operator-2';

    await expect(provider.detach('attachment-1', scope)).resolves.toBeUndefined();
  });

  it('denies expired attachment handles and removes them', async (): Promise<void> => {
    let now = new Date('2026-07-12T00:00:00.000Z');
    const provider = new TmuxFleetRuntimeProvider({
      transport: transport(),
      readAuthority: readAuthority(),
      attachmentIdFactory: (): string => 'attachment-1',
      now: (): Date => now,
    });
    await provider.attach('coder0', 'read', scope);
    now = new Date('2026-07-12T00:05:00.001Z');

    await expect(provider.detach('attachment-1', scope)).rejects.toMatchObject({
      code: 'forbidden',
    } satisfies Partial<FleetRuntimeProviderError>);
    await expect(provider.detach('attachment-1', scope)).rejects.toMatchObject({
      code: 'not_found',
    } satisfies Partial<FleetRuntimeProviderError>);
  });

  it('prunes expired attachment handles before creating a new handle', async (): Promise<void> => {
    let now = new Date('2026-07-12T00:00:00.000Z');
    let attachmentSequence = 0;
    const provider = new TmuxFleetRuntimeProvider({
      transport: transport(),
      readAuthority: readAuthority(),
      attachmentIdFactory: (): string => `attachment-${++attachmentSequence}`,
      now: (): Date => now,
    });
    await provider.attach('coder0', 'read', scope);
    now = new Date('2026-07-12T00:05:00.001Z');
    await provider.attach('coder0', 'read', scope);

    await expect(provider.detach('attachment-1', scope)).rejects.toMatchObject({
      code: 'not_found',
    } satisfies Partial<FleetRuntimeProviderError>);
  });

  it('rejects an empty message before consulting write authority or tmux', async (): Promise<void> => {
    const fleet = transport();
    const writeAuthority: FleetWriteAuthority = {
      canWrite: vi.fn(async (): Promise<boolean> => true),
      assertAuthorized: vi.fn(async (): Promise<void> => undefined),
    };
    const provider = new TmuxFleetRuntimeProvider({ transport: fleet, writeAuthority });

    await expect(
      provider.sendMessage('coder0', { content: '', idempotencyKey: 'message-1' }, scope),
    ).rejects.toMatchObject({
      code: 'invalid_request',
    } satisfies Partial<FleetRuntimeProviderError>);
    expect(writeAuthority.canWrite).not.toHaveBeenCalled();
    expect(writeAuthority.assertAuthorized).not.toHaveBeenCalled();
    expect(fleet.verifySession).not.toHaveBeenCalled();
    expect(fleet.sendMessage).not.toHaveBeenCalled();
  });

  it('denies fleet writes by default before probing the tmux transport', async (): Promise<void> => {
    const fleet = transport();
    const provider = new TmuxFleetRuntimeProvider({ transport: fleet });

    await expect(
      provider.sendMessage('coder0', { content: 'hello', idempotencyKey: 'message-1' }, scope),
    ).rejects.toMatchObject({ code: 'forbidden' } satisfies Partial<FleetRuntimeProviderError>);
    await expect(provider.terminate('coder0', 'approval-1', scope)).rejects.toMatchObject({
      code: 'forbidden',
    } satisfies Partial<FleetRuntimeProviderError>);
    expect(fleet.verifySession).not.toHaveBeenCalled();
    expect(fleet.sendMessage).not.toHaveBeenCalled();
    expect(fleet.terminate).not.toHaveBeenCalled();
  });

  it('rejects an unverified target before consulting orchestrator write authority', async (): Promise<void> => {
    const fleet = transport();
    fleet.verifySession = vi.fn(async (): Promise<FleetRuntimeTarget> => {
      throw new Error('target identity mismatch');
    });
    const writeAuthority: FleetWriteAuthority = {
      canWrite: vi.fn(async (): Promise<boolean> => true),
      assertAuthorized: vi.fn(async (): Promise<void> => undefined),
    };
    const provider = new TmuxFleetRuntimeProvider({ transport: fleet, writeAuthority });

    await expect(
      provider.sendMessage('coder', { content: 'hello', idempotencyKey: 'message-1' }, scope),
    ).rejects.toThrow('target identity mismatch');
    expect(writeAuthority.assertAuthorized).not.toHaveBeenCalled();
    expect(fleet.sendMessage).not.toHaveBeenCalled();
  });

  it('uses the role-neutral interaction source label by default', async (): Promise<void> => {
    const fleet = transport();
    const writeAuthority: FleetWriteAuthority = {
      canWrite: vi.fn(async (): Promise<boolean> => true),
      assertAuthorized: vi.fn(async (): Promise<void> => undefined),
    };
    const provider = new TmuxFleetRuntimeProvider({ transport: fleet, writeAuthority });

    await provider.sendMessage('coder0', { content: 'hello', idempotencyKey: 'message-1' }, scope);

    expect(fleet.sendMessage).toHaveBeenCalledWith('coder0', 'hello', 'interaction');
  });

  it('passes an exact session ID to the fleet transport only through orchestrator-authorized writes', async (): Promise<void> => {
    const fleet = transport();
    const writeAuthority: FleetWriteAuthority = {
      canWrite: vi.fn(async (): Promise<boolean> => true),
      assertAuthorized: vi.fn(async (): Promise<void> => undefined),
    };
    const provider = new TmuxFleetRuntimeProvider({
      transport: fleet,
      sourceLabel: 'operator',
      writeAuthority,
    });

    await provider.sendMessage('coder0', { content: 'hello', idempotencyKey: 'message-1' }, scope);
    await provider.terminate('coder0', 'approval-1', scope);

    expect(writeAuthority.assertAuthorized).toHaveBeenCalledWith({
      operation: 'session.send',
      sessionId: 'coder0',
      scope,
    });
    expect(writeAuthority.assertAuthorized).toHaveBeenCalledWith({
      operation: 'session.terminate',
      sessionId: 'coder0',
      scope,
      approvalRef: 'approval-1',
    });
    expect(fleet.sendMessage).toHaveBeenCalledWith('coder0', 'hello', 'operator');
    expect(fleet.terminate).toHaveBeenCalledWith('coder0');
  });

  it('fails closed when consumers ask for session streaming', async (): Promise<void> => {
    const provider = new TmuxFleetRuntimeProvider({ transport: transport() });
    const stream = provider.streamSession('coder0', undefined, scope)[Symbol.asyncIterator]();

    await expect(stream.next()).rejects.toMatchObject({
      code: 'capability_unsupported',
    } satisfies Partial<FleetRuntimeProviderError>);
  });
});
