import { describe, expect, it } from 'vitest';
import {
  computeFleetHealth,
  contributionBars,
  findUnderperformers,
  fleetMedianHashrate,
  groupBySubnet,
  osArchBreakdown,
  staleAgentIds,
  timeToPayout,
} from './fleetAnalytics';
import type { Agent, PoolStatus } from '../types';

const agent = (overrides: Partial<Agent> = {}): Agent => ({
  id: 'a1',
  name: 'node-1',
  status: 'online',
  hashrate_15m: 1000,
  shares_total: 100,
  shares_good: 98,
  ip: '192.168.1.10',
  platform: 'windows',
  arch: 'amd64',
  last_seen: new Date().toISOString(),
  ...overrides,
} as Agent);

describe('computeFleetHealth', () => {
  it('returns NOMINAL for healthy fleet', () => {
    const agents = [agent(), agent({ id: 'a2', name: 'node-2', hashrate_15m: 900 })];
    const pools: PoolStatus[] = [{ name: 'primary', status: 'green' }];
    const health = computeFleetHealth(agents, pools);
    expect(health.label).toBe('NOMINAL');
    expect(health.score).toBeGreaterThanOrEqual(80);
    expect(health.issues).toHaveLength(0);
  });

  it('flags offline nodes and pool degradation', () => {
    const agents = [agent(), agent({ id: 'a2', status: 'offline', hashrate_15m: 0 })];
    const pools: PoolStatus[] = [{ name: 'primary', status: 'red' }];
    const health = computeFleetHealth(agents, pools);
    expect(health.label).not.toBe('NOMINAL');
    expect(health.issues.some((i) => i.includes('offline'))).toBe(true);
    expect(health.issues.some((i) => i.includes('pool'))).toBe(true);
  });
});

describe('contributionBars', () => {
  it('sorts online agents by hashrate share', () => {
    const bars = contributionBars([
      agent({ id: 'a1', hashrate_15m: 300 }),
      agent({ id: 'a2', hashrate_15m: 700 }),
      agent({ id: 'a3', status: 'offline', hashrate_15m: 9999 }),
    ]);
    expect(bars).toHaveLength(2);
    expect(bars[0].id).toBe('a2');
    expect(bars[0].pct).toBeCloseTo(70, 1);
  });
});

describe('findUnderperformers', () => {
  it('returns agents below 70% of median', () => {
    const agents = [
      agent({ id: 'fast', hashrate_15m: 1000 }),
      agent({ id: 'slow', hashrate_15m: 500 }),
      agent({ id: 'mid', hashrate_15m: 900 }),
    ];
    const under = findUnderperformers(agents);
    expect(under.map((a) => a.id)).toEqual(['slow']);
  });

  it('returns empty when fewer than 2 online miners', () => {
    expect(findUnderperformers([agent()])).toEqual([]);
  });
});

describe('fleetMedianHashrate', () => {
  it('computes median of online non-zero agents', () => {
    const med = fleetMedianHashrate([
      agent({ hashrate_15m: 100 }),
      agent({ id: 'a2', hashrate_15m: 300 }),
      agent({ id: 'a3', hashrate_15m: 200 }),
    ]);
    expect(med).toBe(200);
  });
});

describe('groupBySubnet', () => {
  it('groups by /24 prefix', () => {
    const groups = groupBySubnet([
      agent({ ip: '10.0.0.1' }),
      agent({ id: 'a2', ip: '10.0.0.2' }),
      agent({ id: 'a3', ip: '192.168.5.9' }),
    ]);
    expect(groups).toHaveLength(2);
    expect(groups[0].subnet).toBe('10.0.0.x');
    expect(groups[0].agents).toHaveLength(2);
  });
});

describe('osArchBreakdown', () => {
  it('labels platforms for display', () => {
    const rows = osArchBreakdown([
      agent({ platform: 'linux', arch: 'amd64' }),
      agent({ id: 'a2', platform: 'darwin', arch: 'arm64' }),
    ]);
    expect(rows.some((r) => r.label.includes('Linux'))).toBe(true);
    expect(rows.some((r) => r.label.includes('macOS'))).toBe(true);
  });
});

describe('staleAgentIds', () => {
  it('flags online agents not seen in 5+ minutes', () => {
    const staleTime = new Date(Date.now() - 6 * 60 * 1000).toISOString();
    const ids = staleAgentIds([
      agent({ id: 'fresh', last_seen: new Date().toISOString() }),
      agent({ id: 'stale', last_seen: staleTime }),
    ]);
    expect(ids.has('stale')).toBe(true);
    expect(ids.has('fresh')).toBe(false);
  });
});

describe('timeToPayout', () => {
  it('returns days until payout', () => {
    expect(timeToPayout(0.5, 0.25)).toBe(2);
  });

  it('returns null when data missing', () => {
    expect(timeToPayout(undefined, 1)).toBeNull();
    expect(timeToPayout(1, 0)).toBeNull();
  });
});
