import { describe, expect, it } from 'vitest';
import { readFileSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import type {
  WSAgentLog,
  WSAgentOffline,
  WSCommandResult,
  WSDashboardInit,
  WSMessageTyped,
  WSServerLog,
  WSStatsUpdate,
} from './ws';
import { mockAgent } from '../test/fixtures';

const fixtureDir = dirname(fileURLToPath(import.meta.url));
const goWSFixture = JSON.parse(
  readFileSync(join(fixtureDir, '../../../internal/api/testdata/ws_types_fixture.json'), 'utf8'),
) as Record<string, string[]>;

function expectKeys(obj: Record<string, unknown>, keys: string[]) {
  for (const key of keys) {
    expect(Object.prototype.hasOwnProperty.call(obj, key)).toBe(true);
  }
}

function sampleValue(field: string): unknown {
  if (field === 'agents') return [];
  if (field === 'success') return true;
  if (field === 'line' || field === 'content' || field === 'message' || field === 'action' || field === 'agent_id') {
    return 'sample';
  }
  return 0;
}

function buildSample(fields: string[]): Record<string, unknown> {
  const sample: Record<string, unknown> = {};
  for (const field of fields) {
    sample[field] = field === 'agents' ? [mockAgent()] : sampleValue(field);
  }
  return sample;
}

describe('types/ws payloads', () => {
  it('shared payload keys match Go ws_types.go fixture', () => {
    for (const [typeName, fields] of Object.entries(goWSFixture)) {
      expect(fields).toEqual([...fields].sort());
      const sample = buildSample(fields);
      expectKeys(sample, fields);

      switch (typeName) {
        case 'WSDashboardInit':
          void (sample as WSDashboardInit);
          break;
        case 'WSAgentOffline':
          void (sample as WSAgentOffline);
          break;
        case 'WSStatsUpdate':
          void (sample as WSStatsUpdate);
          break;
        case 'WSCommandResult':
          void (sample as WSCommandResult);
          break;
        case 'WSAgentLog':
          void (sample as WSAgentLog);
          break;
        case 'WSServerLog':
          void (sample as WSServerLog);
          break;
        default:
          throw new Error(`unexpected WS type in Go fixture: ${typeName}`);
      }
    }
  });

  it('WSDashboardInit carries agents array', () => {
    const init: WSDashboardInit = { agents: [mockAgent()] };
    expectKeys(init as unknown as Record<string, unknown>, ['agents']);
    expect(init.agents[0].id).toBe('agent-001-uuid');
  });

  it('WSStatsUpdate includes hashrate fields', () => {
    const stats: WSStatsUpdate = {
      agent_id: 'a1',
      hashrate_15s: 100,
      hashrate_1m: 90,
      hashrate_15m: 80,
      cpu_usage_pct: 12,
    };
    expectKeys(stats as unknown as Record<string, unknown>, [
      'agent_id',
      'hashrate_15s',
      'hashrate_1m',
      'hashrate_15m',
      'cpu_usage_pct',
    ]);
  });

  it('WSAgentOffline and WSCommandResult shape', () => {
    const offline: WSAgentOffline = { agent_id: 'gone' };
    const cmd: WSCommandResult = { agent_id: 'a1', action: 'pause', success: true, message: 'ok' };
    expect(offline.agent_id).toBe('gone');
    expect(cmd.success).toBe(true);
  });

  it('log payloads carry content lines', () => {
    const agentLog: WSAgentLog = { agent_id: 'a1', content: 'line1\nline2' };
    const serverLog: WSServerLog = { line: 'server boot' };
    expect(agentLog.content).toContain('line1');
    expect(serverLog.line).toBe('server boot');
  });

  it('WSMessageTyped pairs type with payload', () => {
    const msg: WSMessageTyped<'stats_update'> = {
      type: 'stats_update',
      payload: {
        agent_id: 'a1',
        hashrate_15s: 1,
        hashrate_1m: 1,
        hashrate_15m: 1,
        cpu_usage_pct: 0,
      },
    };
    expect(msg.type).toBe('stats_update');
    expect((msg.payload as WSStatsUpdate).agent_id).toBe('a1');
  });
});
