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

import path from 'node:path';

import {
    buildProfileMigrationCommand,
    buildProcessDefinitions,
    buildWorkspaceCommands,
    planProfileReconcile,
} from '../src/orchestrator/gatewayOrchestrator.js';
import { sanitizeManagedProcessEnv } from '../src/orchestrator/processManager.js';
import type { GatewayProfileRecord } from '../src/orchestrator/profileRepository.js';

const buildProfile = (buildWorkspace?: string): GatewayProfileRecord => ({
    profileName: 'che:2',
    profile: 'che',
    scenario: '2',
    apiPort: 15003,
    status: 'RUNNING',
    buildStatus: 'SUCCEEDED',
    buildCommitSha: '0123456789abcdef0123456789abcdef01234567',
    buildWorkspace,
    meta: {},
    createdAt: '2026-07-25T00:00:00.000Z',
    updatedAt: '2026-07-25T00:00:00.000Z',
});

describe('planProfileReconcile', () => {
    it('starts missing processes for running profiles', () => {
        expect(
            planProfileReconcile('RUNNING', {
                frontendRunning: true,
                apiRunning: true,
                daemonRunning: false,
                auctionRunning: true,
                battleSimRunning: true,
                tournamentRunning: true,
            })
        ).toEqual({ shouldStart: true, shouldStop: false });
    });

    it('starts processes for preopen profiles', () => {
        expect(
            planProfileReconcile('PREOPEN', {
                frontendRunning: false,
                apiRunning: false,
                daemonRunning: false,
                auctionRunning: false,
                battleSimRunning: false,
                tournamentRunning: false,
            })
        ).toEqual({ shouldStart: true, shouldStop: false });
    });

    it('does nothing when running profile is healthy', () => {
        expect(
            planProfileReconcile('RUNNING', {
                frontendRunning: true,
                apiRunning: true,
                daemonRunning: true,
                auctionRunning: true,
                battleSimRunning: true,
                tournamentRunning: true,
            })
        ).toEqual({ shouldStart: false, shouldStop: false });
    });

    it('restarts a running profile when only the auction worker is missing', () => {
        expect(
            planProfileReconcile('RUNNING', {
                frontendRunning: true,
                apiRunning: true,
                daemonRunning: true,
                auctionRunning: false,
                battleSimRunning: true,
                tournamentRunning: true,
            })
        ).toEqual({ shouldStart: true, shouldStop: false });
    });

    it('stops processes for non-running profiles', () => {
        expect(
            planProfileReconcile('STOPPED', {
                frontendRunning: false,
                apiRunning: false,
                daemonRunning: true,
                auctionRunning: false,
                battleSimRunning: false,
                tournamentRunning: false,
            })
        ).toEqual({ shouldStart: false, shouldStop: true });
    });

    it('keeps reserved profiles off', () => {
        expect(
            planProfileReconcile('RESERVED', {
                frontendRunning: false,
                apiRunning: false,
                daemonRunning: false,
                auctionRunning: false,
                battleSimRunning: false,
                tournamentRunning: false,
            })
        ).toEqual({ shouldStart: false, shouldStop: false });
    });
});

describe('buildProcessDefinitions', () => {
    const processConfig = {
        workspaceRoot: '/srv/sammo/main',
        redisKeyPrefix: 'sammo:gateway',
        gameTokenSecret: 'test-secret',
        gatewayInternalApiUrl: 'http://127.0.0.1:13000',
    };

    it('runs a built profile from its commit worktree', () => {
        const buildWorkspace = '/srv/sammo/worktrees/0123456789abcdef';
        const definitions = buildProcessDefinitions(buildProfile(buildWorkspace), processConfig);

        expect(Object.values(definitions)).toHaveLength(6);
        expect(new Set(Object.values(definitions).map((definition) => definition.name)).size).toBe(6);

        expect(definitions.frontend).toMatchObject({
            cwd: path.join(buildWorkspace, 'app', 'game-frontend'),
            script: path.join(buildWorkspace, 'app', 'game-frontend', 'node_modules', 'vite', 'bin', 'vite.js'),
            args: [
                'preview',
                '--host',
                '0.0.0.0',
                '--port',
                '15002',
                '--outDir',
                path.join(buildWorkspace, '.release-dist', 'che_2', 'game-frontend'),
            ],
        });
        expect(definitions.api.cwd).toBe(path.join(buildWorkspace, 'app', 'game-api'));
        expect(definitions.api.script).toBe(path.join(buildWorkspace, 'app', 'game-api', 'dist', 'index.js'));
        expect(definitions.api.env).toMatchObject({
            GAME_PROFILE_NAME: 'che:2',
            GAME_TRPC_PATH: '/che/api/trpc',
            GAME_API_EVENTS_PATH: '/che/api/events',
            GATEWAY_INTERNAL_API_URL: 'http://127.0.0.1:13000',
            GAME_UPLOAD_PATH: '/che/api/uploads',
        });
        expect(definitions.daemon.cwd).toBe(path.join(buildWorkspace, 'app', 'game-engine'));
        expect(definitions.daemon.script).toBe(path.join(buildWorkspace, 'app', 'game-engine', 'dist', 'index.js'));
        expect(definitions.auction).toMatchObject({
            cwd: path.join(buildWorkspace, 'app', 'game-api'),
            script: path.join(buildWorkspace, 'app', 'game-api', 'dist', 'index.js'),
            env: { GAME_API_ROLE: 'auction-worker' },
        });
        expect(definitions.battleSim).toMatchObject({
            cwd: path.join(buildWorkspace, 'app', 'game-api'),
            script: path.join(buildWorkspace, 'app', 'game-api', 'dist', 'index.js'),
            env: { GAME_API_ROLE: 'battle-sim-worker' },
        });
        expect(definitions.tournament).toMatchObject({
            cwd: path.join(buildWorkspace, 'app', 'game-api'),
            script: path.join(buildWorkspace, 'app', 'game-api', 'dist', 'index.js'),
            env: { GAME_API_ROLE: 'tournament-worker' },
        });
    });

    it('keeps main as the runtime for profiles without a commit worktree', () => {
        const definitions = buildProcessDefinitions(buildProfile(), processConfig);

        expect(definitions.frontend.cwd).toBe(path.join(processConfig.workspaceRoot, 'app', 'game-frontend'));
        expect(definitions.api.cwd).toBe(path.join(processConfig.workspaceRoot, 'app', 'game-api'));
        expect(definitions.daemon.cwd).toBe(path.join(processConfig.workspaceRoot, 'app', 'game-engine'));
        expect(definitions.auction.cwd).toBe(path.join(processConfig.workspaceRoot, 'app', 'game-api'));
        expect(definitions.battleSim.cwd).toBe(path.join(processConfig.workspaceRoot, 'app', 'game-api'));
        expect(definitions.tournament.cwd).toBe(path.join(processConfig.workspaceRoot, 'app', 'game-api'));
    });

    it('does not forward PM2 identity or parent runtime roles to profile processes', () => {
        const definitions = buildProcessDefinitions(buildProfile(), {
            ...processConfig,
            baseEnv: {
                DATABASE_URL: 'postgresql://integration.invalid/sammo',
                VITE_APP_BASE_PATH: '/gateway',
                GATEWAY_ROLE: 'orchestrator',
                args: 'daemon',
                NODE_APP_INSTANCE: '2',
                name: 'sammo:gateway-orchestrator',
                pm_id: '2',
                pm_exec_path: '/srv/controller.js',
            },
        });

        for (const definition of Object.values(definitions)) {
            expect(definition.env).toMatchObject({ DATABASE_URL: 'postgresql://integration.invalid/sammo' });
            expect(definition.env).not.toHaveProperty('pm_id');
            expect(definition.env).not.toHaveProperty('args');
            expect(definition.env).not.toHaveProperty('pm_exec_path');
            expect(definition.env).not.toHaveProperty('name');
            expect(definition.env).not.toHaveProperty('NODE_APP_INSTANCE');
            expect(definition.env).not.toHaveProperty('GATEWAY_ROLE');
        }
        expect(definitions.frontend.env.VITE_APP_BASE_PATH).toBe('/che');
    });
});

describe('sanitizeManagedProcessEnv', () => {
    it('keeps application configuration while removing PM2 metadata and role selectors', () => {
        expect(
            sanitizeManagedProcessEnv({
                DATABASE_URL: 'postgresql://integration.invalid/sammo',
                PATH: '/usr/local/bin:/usr/bin',
                GATEWAY_ROLE: 'orchestrator',
                GAME_API_ROLE: 'server',
                args: 'daemon',
                NODE_APP_INSTANCE: '2',
                name: 'sammo:gateway-orchestrator',
                pm_id: '2',
                pm_cwd: '/srv/controller',
                axm_monitor: '{}',
            })
        ).toEqual({
            DATABASE_URL: 'postgresql://integration.invalid/sammo',
            PATH: '/usr/local/bin:/usr/bin',
        });
    });
});

describe('buildWorkspaceCommands', () => {
    it('installs and builds runtime dependencies before the profile processes', () => {
        const workspaceRoot = '/srv/sammo/worktrees/0123456789abcdef';
        const commands = buildWorkspaceCommands(workspaceRoot, true, undefined, '/srv/sammo/controller');

        expect(commands.map(({ args }) => args)).toEqual([
            ['install', '--frozen-lockfile'],
            [
                'exec',
                'turbo',
                'run',
                'build',
                '--filter=@sammo-ts/game-api',
                '--filter=@sammo-ts/gateway-api',
                '--cache-dir=/srv/sammo/controller/.turbo/release-cache',
                '--concurrency=1',
                '--ui=stream',
                '--output-logs=new-only',
            ],
        ]);
        expect(commands.every(({ cwd }) => cwd === workspaceRoot)).toBe(true);
    });

    it('deploys the game schema migration after building the selected workspace', () => {
        const workspaceRoot = '/srv/sammo/worktrees/0123456789abcdef';
        const databaseUrl = 'postgresql://integration.invalid/sammo?schema=che';
        const command = buildProfileMigrationCommand(workspaceRoot, databaseUrl, { NODE_ENV: 'production' });

        expect(command).toEqual({
            command: 'pnpm',
            args: ['--filter', '@sammo-ts/infra', 'prisma:migrate:deploy:game'],
            cwd: workspaceRoot,
            env: {
                NODE_ENV: 'production',
                DATABASE_URL: databaseUrl,
            },
        });
    });
});
