import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { HealthChecker, type HealthTelemetryEvent } from '@main/services/HealthChecker.js';
import type { InferenceProvider, HealthResult, GenerateInput } from '@main/ai/InferenceProvider.js';
import type { AiResponse } from '@main/ai/schema.js';
import { ProviderHolder } from '@main/ai/ProviderHolder.js';

class FakeProvider implements InferenceProvider {
  readonly name = 'fake';
  results: HealthResult[] = [];
  private idx = 0;
  async healthCheck(): Promise<HealthResult> {
    const r = this.results[Math.min(this.idx, this.results.length - 1)] ?? { ok: true };
    this.idx += 1;
    return r;
  }
  async generate(_input: GenerateInput): Promise<AiResponse> {
    throw new Error('not used');
  }
}

describe('HealthChecker — start/stop polling', () => {
  beforeEach(() => { vi.useFakeTimers(); });
  afterEach(() => { vi.useRealTimers(); });

  it('start() runs runOnce immediately + every intervalMs', async () => {
    const provider = new FakeProvider();
    provider.results = [{ ok: true }, { ok: true }, { ok: true }];
    const hc = new HealthChecker(new ProviderHolder(provider), { intervalMs: 1000 });
    hc.start();
    await vi.runOnlyPendingTimersAsync();
    await vi.advanceTimersByTimeAsync(1000);
    await vi.advanceTimersByTimeAsync(1000);
    expect((provider as any).idx).toBeGreaterThanOrEqual(3);
    hc.stop();
  });

  it('start() is idempotent — second call does not duplicate timer', async () => {
    const provider = new FakeProvider();
    provider.results = [{ ok: true }];
    const hc = new HealthChecker(new ProviderHolder(provider), { intervalMs: 1000 });
    hc.start();
    hc.start();
    // 즉시 1회 + 1s 후 1회 = 정확히 2. 두 timer 가 잘못 등록됐으면 4 (각 timer 마다 즉시+1s).
    await vi.advanceTimersByTimeAsync(1000);
    expect((provider as any).idx).toBe(2);
    hc.stop();
  });

  it('stop() clears timer (no further runOnce)', async () => {
    const provider = new FakeProvider();
    provider.results = [{ ok: true }, { ok: true }];
    const hc = new HealthChecker(new ProviderHolder(provider), { intervalMs: 1000 });
    hc.start();
    await vi.runOnlyPendingTimersAsync();
    const before = (provider as any).idx;
    hc.stop();
    await vi.advanceTimersByTimeAsync(5000);
    expect((provider as any).idx).toBe(before);
  });
});

describe('HealthChecker — delta transitions + telemetry', () => {
  it('ok=true → ok=false 전이 시 onUpdate + ollama_unreachable emit', async () => {
    const provider = new FakeProvider();
    provider.results = [{ ok: true }, { ok: false, reason: 'connection refused' }];
    const updates: HealthResult[] = [];
    const events: HealthTelemetryEvent[] = [];
    const hc = new HealthChecker(new ProviderHolder(provider), {
      onUpdate: (s) => updates.push(s),
      onTelemetry: (e) => events.push(e)
    });
    await hc.runOnce();
    await hc.runOnce();
    expect(updates).toEqual([{ ok: false, reason: 'connection refused' }]);
    expect(events).toEqual([{ kind: 'ollama_unreachable', reason: 'connection refused' }]);
  });

  it('ok=false → ok=true 전이 시 onUpdate + ollama_recovered emit (downtimeMs 정확)', async () => {
    const provider = new FakeProvider();
    provider.results = [{ ok: false, reason: 'refused' }, { ok: true }];
    const events: HealthTelemetryEvent[] = [];
    let nowCounter = 0;
    const hc = new HealthChecker(new ProviderHolder(provider), {
      onTelemetry: (e) => events.push(e),
      now: () => { nowCounter += 1; return nowCounter * 1000; }
    });
    await hc.runOnce();
    await hc.runOnce();
    const recovered = events.find((e) => e.kind === 'ollama_recovered');
    expect(recovered).toEqual({ kind: 'ollama_recovered', downtimeMs: 1000 });
  });

  it('reason 변경만 (ok=false 유지) 시 onUpdate fire 하지만 telemetry emit 안 함', async () => {
    const provider = new FakeProvider();
    provider.results = [
      { ok: false, reason: 'refused' },
      { ok: false, reason: 'timeout' }
    ];
    const updates: HealthResult[] = [];
    const events: HealthTelemetryEvent[] = [];
    const hc = new HealthChecker(new ProviderHolder(provider), {
      onUpdate: (s) => updates.push(s),
      onTelemetry: (e) => events.push(e)
    });
    await hc.runOnce();
    await hc.runOnce();
    expect(updates).toHaveLength(2);
    expect(events).toEqual([{ kind: 'ollama_unreachable', reason: 'refused' }]);
  });

  it('runOnce({manual:true}) 가 ollama_recheck_manual 1회 fire', async () => {
    const provider = new FakeProvider();
    provider.results = [{ ok: true }];
    const events: HealthTelemetryEvent[] = [];
    const hc = new HealthChecker(new ProviderHolder(provider), { onTelemetry: (e) => events.push(e) });
    await hc.runOnce({ manual: true });
    expect(events).toEqual([{ kind: 'ollama_recheck_manual' }]);
  });
});

describe('HealthChecker — ai_enabled gate (v0.2.9 Cut B Task 14)', () => {
  beforeEach(() => { vi.useFakeTimers(); });
  afterEach(() => { vi.useRealTimers(); });

  it('isAiEnabled=false 면 start() polling 이 healthCheck 호출 skip', async () => {
    const provider = new FakeProvider();
    provider.results = [{ ok: true }];
    const hc = new HealthChecker(new ProviderHolder(provider), {
      intervalMs: 1000,
      isAiEnabled: async () => false
    });
    hc.start();
    await vi.advanceTimersByTimeAsync(1000);
    await vi.advanceTimersByTimeAsync(1000);
    // 즉시 + 2 tick = 0회 — AI 비활성으로 모든 polling skip.
    expect((provider as any).idx).toBe(0);
    hc.stop();
  });

  it('isAiEnabled=true 면 polling 정상 (gate 통과)', async () => {
    const provider = new FakeProvider();
    provider.results = [{ ok: true }, { ok: true }];
    const hc = new HealthChecker(new ProviderHolder(provider), {
      intervalMs: 1000,
      isAiEnabled: async () => true
    });
    hc.start();
    // start() 의 즉시 tick 이 microtask 에서 isAiEnabled 를 await 함 → flush 필요.
    await vi.runOnlyPendingTimersAsync();
    await vi.advanceTimersByTimeAsync(1000);
    expect((provider as any).idx).toBeGreaterThanOrEqual(2);
    hc.stop();
  });

  it('isAiEnabled=false 여도 manual runOnce 는 항상 실행 (사용자 의도)', async () => {
    const provider = new FakeProvider();
    provider.results = [{ ok: true }];
    const hc = new HealthChecker(new ProviderHolder(provider), {
      isAiEnabled: async () => false
    });
    await hc.runOnce({ manual: true });
    expect((provider as any).idx).toBe(1);
  });
});
