import { describe, expect, it, vi } from 'vitest';
import type {
  ConnectorExecutionContext,
  ConnectorExecutionGrant,
  ConnectorLease,
  ConnectorLeaseAuditEvent,
  ConnectorLeaseStore,
  FencedConnectorAdapter,
  LogicalAgentBinding,
} from '@mosaicstack/types';
import {
  ConnectorLeaseCoordinator,
  MAX_CONNECTOR_GRANT_TTL_MS,
  MAX_CONNECTOR_LEASE_TTL_MS,
} from './connector-lease.js';
import type { ConnectorLeaseError } from './connector-lease.js';

const identity = { tenantId: 'tenant-a', logicalAgentId: 'mos' } as const;
const binding: LogicalAgentBinding = { identity, bindingId: 'operator-chat' };
const activeLease: ConnectorLease = {
  ...binding,
  leaseId: '00000000-0000-4000-8000-000000000001',
  connectorId: 'connector-a',
  scopes: ['runtime.send', 'tool.execute'],
  leaseEpoch: '3',
  acquiredAt: '2026-07-14T17:00:00.000Z',
  heartbeatAt: '2026-07-14T17:00:00.000Z',
  expiresAt: '2026-07-14T17:10:00.000Z',
};

class FakeLeaseStore implements ConnectorLeaseStore {
  lease: ConnectorLease | null = activeLease;
  readonly audits: ConnectorLeaseAuditEvent[] = [];

  async acquire(): Promise<ConnectorLease> {
    if (!this.lease) throw new Error('fixture has no lease');
    return this.lease;
  }

  async takeover(): Promise<ConnectorLease> {
    if (!this.lease) throw new Error('fixture has no lease');
    return this.lease;
  }

  async heartbeat(): Promise<ConnectorLease> {
    if (!this.lease) throw new Error('fixture has no lease');
    return this.lease;
  }

  async release(): Promise<void> {}

  async findCurrent(): Promise<ConnectorLease | null> {
    return this.lease;
  }

  async recordAudit(event: ConnectorLeaseAuditEvent): Promise<void> {
    this.audits.push(event);
  }
}

describe('ConnectorLeaseCoordinator fencing', (): void => {
  it('validates a server-minted grant immediately before invoking an adapter side effect', async (): Promise<void> => {
    const store = new FakeLeaseStore();
    const coordinator = new ConnectorLeaseCoordinator(store, {
      now: (): Date => new Date('2026-07-14T17:01:00.000Z'),
    });
    const grant = await coordinator.issueGrant({
      lease: activeLease,
      scopes: ['runtime.send'],
      ttlMs: 30_000,
      correlationId: 'correlation-1',
    });
    const execute = vi.fn(async (_input: string, context: ConnectorExecutionContext) => context);
    const adapter: FencedConnectorAdapter<string, ConnectorExecutionContext> = { execute };

    const context = await coordinator.executeGrant(grant, 'runtime.send', 'hello', adapter);

    expect(execute).toHaveBeenCalledOnce();
    expect(context).toMatchObject({
      identity,
      bindingId: 'operator-chat',
      connectorId: 'connector-a',
      leaseEpoch: '3',
      scopes: ['runtime.send'],
    });
  });

  it('caps grant expiry to the durable current lease instead of submitted metadata', async (): Promise<void> => {
    const store = new FakeLeaseStore();
    store.lease = { ...activeLease, expiresAt: '2026-07-14T17:01:05.000Z' };
    const coordinator = new ConnectorLeaseCoordinator(store, {
      now: (): Date => new Date('2026-07-14T17:01:00.000Z'),
    });

    const grant = await coordinator.issueGrant({
      lease: { ...activeLease, expiresAt: '2026-07-14T18:00:00.000Z' },
      scopes: ['runtime.send'],
      ttlMs: 30_000,
      correlationId: 'correlation-durable-expiry',
    });

    expect(grant.expiresAt).toBe('2026-07-14T17:01:05.000Z');
  });

  it.each([
    ['forged clone', (grant: ConnectorExecutionGrant): ConnectorExecutionGrant => ({ ...grant })],
    [
      'cross-tenant clone',
      (grant: ConnectorExecutionGrant): ConnectorExecutionGrant => ({
        ...grant,
        identity: { ...grant.identity, tenantId: 'tenant-b' },
      }),
    ],
    [
      'cross-agent clone',
      (grant: ConnectorExecutionGrant): ConnectorExecutionGrant => ({
        ...grant,
        identity: { ...grant.identity, logicalAgentId: 'other-agent' },
      }),
    ],
    [
      'cross-binding clone',
      (grant: ConnectorExecutionGrant): ConnectorExecutionGrant => ({
        ...grant,
        bindingId: 'other-binding',
      }),
    ],
    [
      'cross-connector clone',
      (grant: ConnectorExecutionGrant): ConnectorExecutionGrant => ({
        ...grant,
        connectorId: 'connector-b',
      }),
    ],
  ])('denies and audits a %s before adapter invocation', async (_label, forge): Promise<void> => {
    const store = new FakeLeaseStore();
    const coordinator = new ConnectorLeaseCoordinator(store, {
      now: (): Date => new Date('2026-07-14T17:01:00.000Z'),
    });
    const grant = await coordinator.issueGrant({
      lease: activeLease,
      scopes: ['runtime.send'],
      ttlMs: 30_000,
      correlationId: 'correlation-forged',
    });
    const adapter = { execute: vi.fn().mockResolvedValue(undefined) };

    await expect(
      coordinator.executeGrant(forge(grant), 'runtime.send', undefined, adapter),
    ).rejects.toMatchObject({ code: 'forged_grant' } satisfies Partial<ConnectorLeaseError>);
    expect(adapter.execute).not.toHaveBeenCalled();
    expect(store.audits.at(-1)).toMatchObject({
      event: 'reject',
      outcome: 'denied',
      reason: 'forged_grant',
      correlationId: 'correlation-forged',
    });
  });

  it('denies malformed forged grants with sanitized audit metadata', async (): Promise<void> => {
    const store = new FakeLeaseStore();
    const coordinator = new ConnectorLeaseCoordinator(store, {
      now: (): Date => new Date('2026-07-14T17:01:00.000Z'),
    });
    const adapter = { execute: vi.fn().mockResolvedValue(undefined) };

    await expect(
      coordinator.executeGrant(
        // @ts-expect-error Deliberately exercise malformed runtime input at the trust boundary.
        {},
        'runtime.send',
        undefined,
        adapter,
      ),
    ).rejects.toMatchObject({ code: 'forged_grant' } satisfies Partial<ConnectorLeaseError>);
    expect(adapter.execute).not.toHaveBeenCalled();
    expect(store.audits).toContainEqual(
      expect.objectContaining({
        identity: { tenantId: 'untrusted', logicalAgentId: 'untrusted' },
        bindingId: 'untrusted',
        connectorId: 'untrusted',
        correlationId: 'untrusted',
        reason: 'forged_grant',
      }),
    );
  });

  it('rejects lease and grant TTLs above server-side safety caps', async (): Promise<void> => {
    const store = new FakeLeaseStore();
    const coordinator = new ConnectorLeaseCoordinator(store, {
      now: (): Date => new Date('2026-07-14T17:01:00.000Z'),
    });
    expect(
      () =>
        new ConnectorLeaseCoordinator(store, {
          maxLeaseTtlMs: MAX_CONNECTOR_LEASE_TTL_MS + 1,
        }),
    ).toThrow(/no greater than/);

    await expect(
      coordinator.acquire({
        identity,
        bindingId: 'operator-chat',
        connectorId: 'connector-a',
        scopes: ['runtime.send'],
        ttlMs: MAX_CONNECTOR_LEASE_TTL_MS + 1,
        correlationId: 'correlation-ttl',
      }),
    ).rejects.toThrow(/no greater than/);
    await expect(
      coordinator.issueGrant({
        lease: activeLease,
        scopes: ['runtime.send'],
        ttlMs: MAX_CONNECTOR_GRANT_TTL_MS + 1,
        correlationId: 'correlation-ttl',
      }),
    ).rejects.toThrow(/no greater than/);
  });

  it('denies stale epoch, expired grant, and unauthorized scope before side effects', async (): Promise<void> => {
    let now = new Date('2026-07-14T17:01:00.000Z');
    const store = new FakeLeaseStore();
    const coordinator = new ConnectorLeaseCoordinator(store, { now: (): Date => now });
    const stale = await coordinator.issueGrant({
      lease: activeLease,
      scopes: ['runtime.send'],
      ttlMs: 30_000,
      correlationId: 'correlation-stale',
    });
    store.lease = { ...activeLease, leaseEpoch: '4', connectorId: 'connector-b' };
    const adapter = { execute: vi.fn().mockResolvedValue(undefined) };

    await expect(
      coordinator.executeGrant(stale, 'runtime.send', undefined, adapter),
    ).rejects.toMatchObject({ code: 'stale_epoch' } satisfies Partial<ConnectorLeaseError>);

    store.lease = activeLease;
    const expiring = await coordinator.issueGrant({
      lease: activeLease,
      scopes: ['runtime.send'],
      ttlMs: 1_000,
      correlationId: 'correlation-expired',
    });
    now = new Date('2026-07-14T17:01:02.000Z');
    await expect(
      coordinator.executeGrant(expiring, 'runtime.send', undefined, adapter),
    ).rejects.toMatchObject({ code: 'grant_expired' } satisfies Partial<ConnectorLeaseError>);

    now = new Date('2026-07-14T17:01:00.000Z');
    const scoped = await coordinator.issueGrant({
      lease: activeLease,
      scopes: ['runtime.send'],
      ttlMs: 30_000,
      correlationId: 'correlation-scope',
    });
    await expect(
      coordinator.executeGrant(scoped, 'tool.execute', undefined, adapter),
    ).rejects.toMatchObject({ code: 'scope_denied' } satisfies Partial<ConnectorLeaseError>);
    expect(adapter.execute).not.toHaveBeenCalled();
  });
});
