import fs from 'node:fs';
import path from 'node:path';
import { describe, expect, it } from 'vitest';
import { REGISTRY_COMMAND_NAMES } from '../../src/contracts/websocket-commands.js';

function readRepoFile(...segments: string[]): string {
  return fs.readFileSync(path.resolve(process.cwd(), '..', ...segments), 'utf8');
}

function extractSwiftCommandTypes(source: string): string[] {
  const envelope = [...source.matchAll(/envelope\(type:\s*"([^"]+)"/g)].map((m) => m[1]);
  const typeOnly = [...source.matchAll(/typeOnly\("([^"]+)"/g)].map((m) => m[1]);
  const initFn = source.includes('static func connect(userID:') ? ['init'] : [];
  return [...new Set([...envelope, ...typeOnly, ...initFn])];
}

function extractKotlinCommandTypes(source: string): string[] {
  const envelope = [...source.matchAll(/envelope\(\s*"([^"]+)"/g)].map((m) => m[1]);
  const typeOnly = [...source.matchAll(/mapOf\("type"\s+to\s+"([^"]+)"\)/g)].map((m) => m[1]);
  const topLevel = [...source.matchAll(/"type"\s+to\s+"([^"]+)"/g)].map((m) => m[1]);
  return [...new Set([...envelope, ...typeOnly, ...topLevel])];
}

describe('iOS websocket client contract', () => {
  const constants = readRepoFile('ios', 'lichunWebsocket', 'Shared', 'Utilities', 'Constants.swift');
  const builderCommands = extractSwiftCommandTypes(constants);

  it('defines WebSocketCommands enum with core ID commands', () => {
    expect(constants).toContain('enum WebSocketCommands');
    for (const cmd of ['quitHabit', 'applyForJob', 'purchaseItem', 'debugGrant']) {
      expect(builderCommands).toContain(cmd);
    }
    expect(constants).toContain('initSession');
  });

  it('builder commands are registered in manifest', () => {
    for (const cmd of builderCommands) {
      if (cmd === 'init') continue;
      expect(REGISTRY_COMMAND_NAMES, `missing manifest entry for iOS builder ${cmd}`).toContain(cmd);
    }
  });

  it('handles canonical server response types for mutating commands', () => {
    const combined = [
      readRepoFile('ios', 'lichunWebsocket', 'Core', 'Services', 'GameStateStore.swift'),
      readRepoFile('ios', 'lichunWebsocket', 'Core', 'Services', 'MonetizationService.swift'),
    ].join('\n');

    for (const type of ['habitQuitting', 'jobApplied', 'jobQuit', 'purchaseComplete']) {
      expect(combined).toContain(`"${type}"`);
    }
  });
});

describe('Android websocket client contract', () => {
  const commands = readRepoFile(
    'android',
    'app',
    'src',
    'main',
    'java',
    'com',
    'craigvg',
    'lichun_android',
    'utils',
    'WebSocketCommands.kt'
  );
  const builderCommands = extractKotlinCommandTypes(commands);

  it('defines WebSocketCommands object with migrated commands', () => {
    expect(commands).toContain('object WebSocketCommands');
    for (const cmd of ['debugSetup', 'debugGrant', 'quitHabit', 'conversation']) {
      expect(builderCommands).toContain(cmd);
    }
  });

  it('builder commands are registered in manifest', () => {
    for (const cmd of builderCommands) {
      if (cmd === 'init') continue;
      expect(REGISTRY_COMMAND_NAMES, `missing manifest entry for Android builder ${cmd}`).toContain(cmd);
    }
  });
});

describe('response coverage', () => {
  it('mutating command success responses are handled on iOS', () => {
    const sources = [
      readRepoFile('ios', 'lichunWebsocket', 'Core', 'Services', 'GameStateStore.swift'),
      readRepoFile('ios', 'lichunWebsocket', 'Core', 'Services', 'MonetizationService.swift'),
      readRepoFile('ios', 'lichunWebsocket', 'Core', 'Services', 'RetentionService.swift'),
      readRepoFile('ios', 'lichunWebsocket', 'Core', 'Services', 'EventService.swift'),
      readRepoFile('ios', 'lichunWebsocket', 'WebSocketService.swift'),
    ].join('\n');

    const requiredSuccessResponses = [
      'habitQuitting',
      'habitQuitStopped',
      'jobApplied',
      'jobQuit',
      'purchaseComplete',
      'extracurricularApplied',
      'focusUpdated',
      'inAppPurchaseComplete',
      'dailyRewardClaimed',
      'questRewardClaimed',
      'onboardingComplete',
      'debugSetupComplete',
      'debugGrantComplete',
    ];

    const missing = requiredSuccessResponses.filter((type) => !sources.includes(`"${type}"`));
    expect(missing).toEqual([]);
  });

  it('mutating command success responses are handled on Android', () => {
    const ws = readRepoFile(
      'android',
      'app',
      'src',
      'main',
      'java',
      'com',
      'craigvg',
      'lichun_android',
      'network',
      'WebSocketManager.kt'
    );

    const required = [
      'habitQuitting',
      'habitQuitStopped',
      'jobApplied',
      'jobQuit',
      'extracurricularApplied',
      'extracurricularQuit',
      'focusUpdated',
      'purchaseComplete',
      'inAppPurchaseComplete',
      'dailyRewardClaimed',
      'questRewardClaimed',
      'onboardingComplete',
      'debugSetupComplete',
      'debugGrantComplete',
    ];

    const missing = required.filter((type) => !ws.includes(`"${type}"`));
    expect(missing).toEqual([]);
  });
});
