import { Command } from 'commander';
import { describe, expect, it, vi } from 'vitest';
import type { FleetRoster } from './fleet.js';
import { TmuxPromotionTransport } from '../fleet/promotion-transport.js';
import {
  promoteSeat,
  registerPromoteCommand,
  type PromotionBreadcrumbStore,
  type PromotionResult,
  type PromotionTransport,
} from './promote.js';

const attemptId = 'a'.repeat(64);
const target = {
  bundle: 'local',
  seat: 'claude-seat',
  sessionId: 'b'.repeat(64),
};

function transport(): PromotionTransport {
  return {
    resolve: vi.fn(async () => target),
    sendPromotion: vi.fn(async () => {}),
  };
}

describe('mosaic promote', () => {
  it('accepts only a fresh result correlated to this attempt', async () => {
    const promotionTransport = transport();
    const store: PromotionBreadcrumbStore = {
      readAttemptId: vi.fn().mockResolvedValueOnce(null).mockResolvedValue(attemptId),
      readResult: vi
        .fn()
        .mockResolvedValueOnce({
          attempt_id: 'c'.repeat(64),
          expires_at_wallclock: 4_600,
          reason: null,
          session_id: target.sessionId,
          ts: 1_001,
          verified: true,
        })
        .mockResolvedValueOnce({
          attempt_id: attemptId,
          expires_at_wallclock: 4_600,
          reason: null,
          session_id: target.sessionId,
          ts: 1_001,
          verified: true,
        }),
    };

    const result = await promoteSeat('claude-seat', {
      clock: () => 1_000,
      sleep: async () => {},
      store,
      timeoutMs: 1,
      transport: promotionTransport,
    });

    expect(result).toEqual({
      bundle: 'local',
      expiresAtWallclock: 4_600,
      reason: null,
      seat: 'claude-seat',
      sessionId: 'b'.repeat(64),
      status: 'VERIFIED',
    });
    expect(promotionTransport.sendPromotion).toHaveBeenCalledWith(target);
    expect(store.readResult).toHaveBeenCalledTimes(2);
  });

  it('accepts a fresh result for this session when completion consumed the pending nonce', async () => {
    const promotionTransport = transport();
    let now = 1_000;
    const store: PromotionBreadcrumbStore = {
      readAttemptId: vi.fn(async () => null),
      readResult: vi.fn(async () => ({
        attempt_id: attemptId,
        expires_at_wallclock: 4_600,
        reason: null,
        session_id: target.sessionId,
        ts: 1_001,
        verified: true,
      })),
    };

    const result = await promoteSeat('claude-seat', {
      clock: () => now,
      sleep: async () => {
        now += 10;
      },
      store,
      timeoutMs: 10,
      transport: promotionTransport,
    });

    expect(result.status).toBe('VERIFIED');
  });

  it('returns UNVERIFIED within the command bound when a tmux runner wedges', async () => {
    vi.useFakeTimers();
    const roster: FleetRoster = {
      agents: [{ className: 'worker', name: 'claude-seat', runtime: 'claude' }],
      defaults: { workingDirectory: '~/src' },
      runtimes: {},
      tmux: { holderSession: '_holder', socketName: 'mosaic-fleet' },
      transport: 'tmux',
      version: 1,
    };
    const promotionTransport = new TmuxPromotionTransport({
      mosaicHome: '/mosaic',
      rosterLoader: async () => roster,
      runner: async () => new Promise(() => {}),
    });
    const store: PromotionBreadcrumbStore = {
      readAttemptId: vi.fn(async () => null),
      readResult: vi.fn(async () => null),
    };

    try {
      let observedResult: PromotionResult | undefined;
      void promoteSeat('claude-seat', {
        store,
        transport: promotionTransport,
      }).then((result) => {
        observedResult = result;
      });
      await vi.advanceTimersByTimeAsync(5_000);

      expect(observedResult).toMatchObject({
        reason: 'RESOLVE_FAILED: Promotion transport command timed out after 5000ms.',
        seat: 'claude-seat',
        status: 'UNVERIFIED',
      });
    } finally {
      vi.useRealTimers();
    }
  });

  it('prints VERIFIED with the resolved seat, session, bundle, and wall-clock expiry', async () => {
    const promotionTransport = transport();
    const store: PromotionBreadcrumbStore = {
      readAttemptId: vi.fn().mockResolvedValueOnce(null).mockResolvedValue(attemptId),
      readResult: vi.fn(async () => ({
        attempt_id: attemptId,
        expires_at_wallclock: 4_600,
        reason: null,
        session_id: target.sessionId,
        ts: Number.MAX_SAFE_INTEGER,
        verified: true,
      })),
    };
    const output = vi.spyOn(console, 'log').mockImplementation(() => {});
    const program = new Command().exitOverride();
    registerPromoteCommand(program, {
      mintAuthorization: async () => {},
      store,
      transport: promotionTransport,
    });

    try {
      await program.parseAsync(['node', 'mosaic', 'promote', 'claude-seat']);
      expect(output).toHaveBeenCalledWith(
        `VERIFIED seat=claude-seat session=${target.sessionId} bundle=local expiry=1970-01-01T01:16:40.000Z`,
      );
      expect(process.exitCode).not.toBe(1);
    } finally {
      output.mockRestore();
      process.exitCode = undefined;
    }
  });

  it('prints UNVERIFIED and exits 1 when delivery fails', async () => {
    const promotionTransport: PromotionTransport = {
      resolve: vi.fn(async () => target),
      sendPromotion: vi.fn(async () => {
        throw new Error('tmux unavailable');
      }),
    };
    const store: PromotionBreadcrumbStore = {
      readAttemptId: vi.fn(async () => null),
      readResult: vi.fn(async () => null),
    };
    const output = vi.spyOn(console, 'log').mockImplementation(() => {});
    const program = new Command().exitOverride();
    registerPromoteCommand(program, {
      mintAuthorization: async () => {},
      store,
      transport: promotionTransport,
    });

    try {
      process.exitCode = undefined;
      await program.parseAsync(['node', 'mosaic', 'promote', 'claude-seat']);
      expect(output).toHaveBeenCalledWith(
        `UNVERIFIED seat=claude-seat session=${target.sessionId} bundle=local expiry=none reason=DELIVERY_FAILED: tmux unavailable`,
      );
      expect(process.exitCode).toBe(1);
    } finally {
      output.mockRestore();
      process.exitCode = undefined;
    }
  });

  it('rejects a stale result even when its nonce matches', async () => {
    const promotionTransport = transport();
    let now = 1_000;
    const store: PromotionBreadcrumbStore = {
      readAttemptId: vi.fn().mockResolvedValueOnce(null).mockResolvedValue(attemptId),
      readResult: vi.fn(async () => ({
        attempt_id: attemptId,
        expires_at_wallclock: 4_600,
        reason: null,
        session_id: target.sessionId,
        ts: 1_000,
        verified: true,
      })),
    };

    const result = await promoteSeat('claude-seat', {
      clock: () => now,
      sleep: async () => {
        now += 10;
      },
      store,
      timeoutMs: 10,
      transport: promotionTransport,
    });

    expect(result).toEqual({
      bundle: 'local',
      expiresAtWallclock: null,
      reason: 'PROMOTION_TIMEOUT',
      seat: 'claude-seat',
      sessionId: 'b'.repeat(64),
      status: 'UNVERIFIED',
    });
  });

  it('does not accept a result for a pending attempt that existed before send', async () => {
    const promotionTransport = transport();
    let now = 1_000;
    const store: PromotionBreadcrumbStore = {
      readAttemptId: vi.fn(async () => attemptId),
      readResult: vi.fn(async () => ({
        attempt_id: attemptId,
        expires_at_wallclock: 4_600,
        reason: null,
        session_id: target.sessionId,
        ts: 1_001,
        verified: true,
      })),
    };

    const result = await promoteSeat('claude-seat', {
      clock: () => now,
      sleep: async () => {
        now += 10;
      },
      store,
      timeoutMs: 10,
      transport: promotionTransport,
    });

    expect(result.status).toBe('UNVERIFIED');
    expect(store.readResult).not.toHaveBeenCalled();
  });

  it('returns UNVERIFIED after a bounded timeout without reading stdin', async () => {
    const promotionTransport = transport();
    let now = 1_000;
    const store: PromotionBreadcrumbStore = {
      readAttemptId: vi.fn(async () => null),
      readResult: vi.fn(async () => null),
    };

    const result = await promoteSeat('claude-seat', {
      clock: () => now,
      sleep: async () => {
        now += 10;
      },
      store,
      timeoutMs: 10,
      transport: promotionTransport,
    });

    expect(result).toEqual({
      bundle: 'local',
      expiresAtWallclock: null,
      reason: 'PROMOTION_TIMEOUT',
      seat: 'claude-seat',
      sessionId: 'b'.repeat(64),
      status: 'UNVERIFIED',
    });
    expect(promotionTransport.sendPromotion).toHaveBeenCalledOnce();
    expect(store.readResult).toHaveBeenCalledTimes(2);
  });
});
