import type { Agent, PoolStatus } from '../types';

// ─── Types ───────────────────────────────────────────────────────────────────

export interface FleetHealth {
  score: number;             // 0–100
  color: 'green' | 'amber' | 'red';
  label: 'NOMINAL' | 'DEGRADED' | 'CRITICAL';
  sentence: string;          // One human-readable summary line
  issues: string[];          // Short issue fragments that compose the sentence
}

export interface ContributionBar {
  id: string;
  name: string;
  hashrate: number;
  pct: number;               // 0–100
}

export interface SubnetGroup {
  subnet: string;
  agents: Agent[];
}

export interface PlatformCount {
  key: string;
  label: string;             // Human-friendly: Win/Linux/macOS + arch
  count: number;
}

// ─── Health Score ─────────────────────────────────────────────────────────────

/**
 * Weighted fleet health (0–100):
 *   Online %        40 pts
 *   Accept rate     30 pts
 *   Pool connected  20 pts
 *   Has hashrate    10 pts
 */
export function computeFleetHealth(agents: Agent[], pools: PoolStatus[]): FleetHealth {
  const issues: string[] = [];
  let score = 0;

  // 40 pts — online ratio
  const online = agents.filter((a) => a.status === 'online');
  const onlinePct = agents.length > 0 ? (online.length / agents.length) * 100 : 100;
  score += (onlinePct / 100) * 40;
  if (agents.length > 0 && onlinePct < 100) {
    issues.push(`${agents.length - online.length} node${agents.length - online.length !== 1 ? 's' : ''} offline`);
  }

  // 30 pts — share accept rate
  const totalShares = agents.reduce((s, a) => s + a.shares_total, 0);
  const goodShares = agents.reduce((s, a) => s + a.shares_good, 0);
  const acceptRate = totalShares > 0 ? (goodShares / totalShares) * 100 : 100;
  score += (acceptRate / 100) * 30;
  if (totalShares > 10 && acceptRate < 95) {
    issues.push(`${(100 - acceptRate).toFixed(1)}% rejection rate`);
  }

  // 20 pts — pool green
  const poolGreen = pools.length === 0 || pools.some((p) => p.status === 'green');
  score += poolGreen ? 20 : 0;
  if (pools.length > 0 && !poolGreen) {
    issues.push('pool connection degraded');
  }

  // 10 pts — hashrate > 0
  const totalHash = agents.reduce((s, a) => s + a.hashrate_15m, 0);
  score += totalHash > 0 ? 10 : 0;
  if (agents.length > 0 && totalHash === 0) {
    issues.push('no hashrate detected');
  }

  const rounded = Math.round(score);
  const color = rounded >= 80 ? 'green' : rounded >= 55 ? 'amber' : 'red';
  const label = rounded >= 80 ? 'NOMINAL' : rounded >= 55 ? 'DEGRADED' : 'CRITICAL';
  const sentence =
    issues.length === 0
      ? 'All systems nominal — fleet is mining at full capacity.'
      : issues.join(' · ');

  return { score: rounded, color, label, sentence, issues };
}

// ─── Contribution Map ─────────────────────────────────────────────────────────

export function contributionBars(agents: Agent[]): ContributionBar[] {
  const online = agents.filter((a) => a.status === 'online');
  const total = online.reduce((s, a) => s + a.hashrate_15m, 0);
  return online
    .map((a) => ({
      id: a.id,
      name: a.name,
      hashrate: a.hashrate_15m,
      pct: total > 0 ? (a.hashrate_15m / total) * 100 : 0,
    }))
    .sort((a, b) => b.hashrate - a.hashrate);
}

// ─── Underperformers ─────────────────────────────────────────────────────────

function median(values: number[]): number {
  if (values.length === 0) return 0;
  const sorted = [...values].sort((a, b) => a - b);
  const mid = Math.floor(sorted.length / 2);
  return sorted.length % 2 === 0
    ? (sorted[mid - 1] + sorted[mid]) / 2
    : sorted[mid];
}

/**
 * Returns online agents whose 15m hashrate is below 70 % of the fleet median.
 * Requires at least 2 online agents with non-zero hash to be meaningful.
 */
export function findUnderperformers(agents: Agent[]): Agent[] {
  const online = agents.filter((a) => a.status === 'online' && a.hashrate_15m > 0);
  if (online.length < 2) return [];
  const med = median(online.map((a) => a.hashrate_15m));
  return online.filter((a) => a.hashrate_15m < med * 0.7);
}

export function fleetMedianHashrate(agents: Agent[]): number {
  const online = agents.filter((a) => a.status === 'online' && a.hashrate_15m > 0);
  return median(online.map((a) => a.hashrate_15m));
}

// ─── LAN Grouping ─────────────────────────────────────────────────────────────

/** Group agents by /24 subnet (first 3 octets). Uses the same logic as fleetFilters. */
export function groupBySubnet(agents: Agent[]): SubnetGroup[] {
  const map = new Map<string, Agent[]>();
  for (const a of agents) {
    const ip = (a.ip || '').trim();
    const parts = ip.split('.');
    const subnet = parts.length >= 3 ? `${parts[0]}.${parts[1]}.${parts[2]}.x` : 'unrouted';
    if (!map.has(subnet)) map.set(subnet, []);
    map.get(subnet)!.push(a);
  }
  return [...map.entries()]
    .sort((a, b) => b[1].length - a[1].length)
    .map(([subnet, ags]) => ({ subnet, agents: ags }));
}

// ─── OS / Arch Breakdown ──────────────────────────────────────────────────────

export function osArchBreakdown(agents: Agent[]): PlatformCount[] {
  const map = new Map<string, number>();
  for (const a of agents) {
    const plat = a.platform || 'unknown';
    const arch = a.arch || '';
    const key = arch ? `${plat}/${arch}` : plat;
    map.set(key, (map.get(key) ?? 0) + 1);
  }
  return [...map.entries()]
    .sort((a, b) => b[1] - a[1])
    .map(([key, count]) => ({
      key,
      label: key
        .replace('windows', 'Win')
        .replace('linux', 'Linux')
        .replace('darwin', 'macOS'),
      count,
    }));
}

// ─── Staleness ────────────────────────────────────────────────────────────────

const STALE_MS = 5 * 60 * 1000; // 5 minutes

/**
 * Returns a Set of agent IDs that claim "online" but haven't been seen
 * in more than 5 minutes — silently dead before the offline alert fires.
 */
export function staleAgentIds(agents: Agent[]): Set<string> {
  const now = Date.now();
  return new Set(
    agents
      .filter(
        (a) =>
          a.status === 'online' &&
          a.last_seen &&
          now - new Date(a.last_seen).getTime() > STALE_MS,
      )
      .map((a) => a.id),
  );
}

// ─── Time to Payout ───────────────────────────────────────────────────────────

/** Returns days until next payout, or null if insufficient data. */
export function timeToPayout(pendingXmr: number | undefined, xmrPerDay: number | undefined): number | null {
  if (!pendingXmr || !xmrPerDay || xmrPerDay <= 0 || pendingXmr <= 0) return null;
  return pendingXmr / xmrPerDay;
}
