import { describe, expect, it } from 'vitest';

import { GatewayOrchestrator } from '../src/orchestrator/gatewayOrchestrator.js';
import type { ProcessDefinition, ProcessManager } from '../src/orchestrator/processManager.js';
import type {
    GatewayOperationRecord,
    GatewayOperationStatus,
    GatewayProfileRecord,
    GatewayProfileRepository,
} from '../src/orchestrator/profileRepository.js';
import { GitWorkspaceManager } from '../src/orchestrator/workspaceManager.js';

const profile: GatewayProfileRecord = {
    profileName: 'che:2',
    profile: 'che',
    scenario: '2',
    apiPort: 15003,
    status: 'STOPPED',
    buildStatus: 'SUCCEEDED',
    buildCommitSha: '0123456789abcdef0123456789abcdef01234567',
    buildWorkspace: '/srv/sammo/worktrees/0123456789abcdef',
    meta: {},
    createdAt: '2026-07-25T00:00:00.000Z',
    updatedAt: '2026-07-25T00:00:00.000Z',
};

const buildOperation = (type: 'START' | 'STOP'): GatewayOperationRecord => ({
    id: '11111111-1111-4111-8111-111111111111',
    profileName: profile.profileName,
    type,
    status: 'RUNNING',
    payload: {},
    requestedBy: 'admin',
    createdAt: '2026-07-25T01:00:00.000Z',
    startedAt: '2026-07-25T01:00:00.000Z',
    updatedAt: '2026-07-25T01:00:00.000Z',
});

const createHarness = (
    operation: GatewayOperationRecord,
    failStart = false,
    failStop = false,
    processesPresent = true,
    missingOnDelete = false
) => {
    let nextOperation: GatewayOperationRecord | null = operation;
    const statuses: string[] = [];
    const completions: GatewayOperationStatus[] = [];
    const started: ProcessDefinition[] = [];
    const stopped: string[] = [];
    const deleted: string[] = [];

    const repository: GatewayProfileRepository = {
        listProfiles: async () => [profile],
        getProfile: async () => profile,
        upsertProfile: async () => profile,
        updateScenario: async () => profile,
        updateStatus: async (_profileName, status) => {
            statuses.push(status);
            return { ...profile, status };
        },
        updateBuildStatus: async () => profile,
        updateMeta: async () => profile,
        listReservedToStart: async () => [],
        findQueuedBuild: async () => null,
        updateLastError: async () => {},
        updateWorkspaceUsage: async () => {},
        clearWorkspaceUsage: async () => {},
        listOperations: async () => [],
        getOperation: async () => operation,
        createOperation: async () => operation,
        claimNextOperation: async () => {
            const result = nextOperation;
            nextOperation = null;
            return result;
        },
        completeOperation: async (_id, status) => {
            completions.push(status);
            return { ...operation, status };
        },
        requeueOperation: async () => ({ ...operation, status: 'QUEUED' }),
        cancelOperation: async () => false,
        retryOperation: async () => null,
    };
    const processManager: ProcessManager = {
        list: async () =>
            processesPresent
                ? [
                      { name: 'sammo:che:2:game-api', status: 'online' },
                      { name: 'sammo:che:2:turn-daemon', status: 'online' },
                      { name: 'sammo:che:2:auction-worker', status: 'online' },
                      { name: 'sammo:che:2:battle-sim-worker', status: 'online' },
                      { name: 'sammo:che:2:tournament-worker', status: 'online' },
                  ]
                : [],
        start: async (definition) => {
            if (failStart) {
                throw new Error('pm2 unavailable');
            }
            started.push(definition);
        },
        stop: async (name) => {
            stopped.push(name);
            if (failStop) {
                throw new Error('pm2 stop failed');
            }
        },
        delete: async (name) => {
            deleted.push(name);
            if (missingOnDelete) {
                throw new Error('process or namespace not found');
            }
            if (failStop) {
                throw new Error('pm2 delete failed');
            }
        },
    };
    const orchestrator = new GatewayOrchestrator({
        repository,
        processManager,
        buildRunner: {
            run: async () => ({ ok: true, exitCode: 0, output: '' }),
        },
        workspaceManager: new GitWorkspaceManager({
            repoRoot: '/tmp/not-used',
            worktreeRoot: '/tmp/not-used-worktrees',
        }),
        processConfig: {
            workspaceRoot: '/srv/sammo',
            redisKeyPrefix: 'sammo:test',
            gameTokenSecret: 'test-secret',
        },
        reconcileIntervalMs: 60_000,
        scheduleIntervalMs: 60_000,
        buildIntervalMs: 60_000,
        adminActionIntervalMs: 60_000,
    });

    return { orchestrator, statuses, completions, started, stopped, deleted };
};

describe('GatewayOrchestrator first-class operations', () => {
    it('starts every profile process and records success', async () => {
        const harness = createHarness(buildOperation('START'));

        await harness.orchestrator.runOperationsNow();

        expect(harness.statuses).toEqual(['RUNNING']);
        expect(harness.started.map((definition) => definition.name)).toEqual([
            'sammo:che:2:game-api',
            'sammo:che:2:turn-daemon',
            'sammo:che:2:auction-worker',
            'sammo:che:2:battle-sim-worker',
            'sammo:che:2:tournament-worker',
        ]);
        expect(harness.completions).toEqual(['SUCCEEDED']);
    });

    it('stops every profile process and records success', async () => {
        const harness = createHarness(buildOperation('STOP'));

        await harness.orchestrator.runOperationsNow();

        expect(harness.statuses).toEqual(['STOPPED']);
        expect(harness.stopped).toEqual([
            'sammo:che:2:game-api',
            'sammo:che:2:turn-daemon',
            'sammo:che:2:auction-worker',
            'sammo:che:2:battle-sim-worker',
            'sammo:che:2:tournament-worker',
        ]);
        expect(harness.deleted).toEqual([
            'sammo:che:2:game-api',
            'sammo:che:2:turn-daemon',
            'sammo:che:2:auction-worker',
            'sammo:che:2:battle-sim-worker',
            'sammo:che:2:tournament-worker',
        ]);
        expect(harness.completions).toEqual(['SUCCEEDED']);
    });

    it('treats an already stopped profile as a successful idempotent stop', async () => {
        const harness = createHarness(buildOperation('STOP'), false, false, false);

        await harness.orchestrator.runOperationsNow();

        expect(harness.statuses).toEqual(['STOPPED']);
        expect(harness.stopped).toEqual([]);
        expect(harness.deleted).toEqual([]);
        expect(harness.completions).toEqual(['SUCCEEDED']);
    });

    it('treats a process removed concurrently as a successful stop', async () => {
        const harness = createHarness(buildOperation('STOP'), false, false, true, true);

        await harness.orchestrator.runOperationsNow();

        expect(harness.deleted).toEqual([
            'sammo:che:2:game-api',
            'sammo:che:2:turn-daemon',
            'sammo:che:2:auction-worker',
            'sammo:che:2:battle-sim-worker',
            'sammo:che:2:tournament-worker',
        ]);
        expect(harness.completions).toEqual(['SUCCEEDED']);
    });

    it('records a failed start instead of reporting a false success', async () => {
        const harness = createHarness(buildOperation('START'), true);

        await harness.orchestrator.runOperationsNow();

        expect(harness.completions).toEqual(['FAILED']);
    });

    it('attempts to stop every role before reporting a partial PM2 failure', async () => {
        const harness = createHarness(buildOperation('STOP'), false, true);

        await harness.orchestrator.runOperationsNow();

        expect(harness.stopped).toEqual([
            'sammo:che:2:game-api',
            'sammo:che:2:turn-daemon',
            'sammo:che:2:auction-worker',
            'sammo:che:2:battle-sim-worker',
            'sammo:che:2:tournament-worker',
        ]);
        expect(harness.deleted).toEqual([
            'sammo:che:2:game-api',
            'sammo:che:2:turn-daemon',
            'sammo:che:2:auction-worker',
            'sammo:che:2:battle-sim-worker',
            'sammo:che:2:tournament-worker',
        ]);
        expect(harness.completions).toEqual(['FAILED']);
    });
});
