import { describe, it, expect, vi } from 'vitest';
import { isVisionCapable, refreshVisionCache } from '@main/services/VisionDetect.js';
import type { OllamaModel } from '@main/services/VisionDetect.js';

// ---------------------------------------------------------------------------
// isVisionCapable
// ---------------------------------------------------------------------------
describe('isVisionCapable', () => {
  it('returns true when details.family is in VISION_FAMILIES', () => {
    const model: OllamaModel = { name: 'some-model', details: { family: 'llava' } };
    expect(isVisionCapable(model)).toBe(true);
  });

  it('returns true when details.families contains a vision family', () => {
    const model: OllamaModel = { name: 'some-model', details: { families: ['text', 'minicpm-v'] } };
    expect(isVisionCapable(model)).toBe(true);
  });

  it('returns true when name contains a vision hint (case-insensitive)', () => {
    const model: OllamaModel = { name: 'My-Vision-Model:latest' };
    expect(isVisionCapable(model)).toBe(true);
  });

  it('returns true when name contains "vl" hint', () => {
    const model: OllamaModel = { name: 'qwen2-vl:7b' };
    expect(isVisionCapable(model)).toBe(true);
  });

  it('returns false for a plain text model with no vision signals', () => {
    const model: OllamaModel = { name: 'gemma2:9b', details: { family: 'gemma', families: ['gemma'] } };
    expect(isVisionCapable(model)).toBe(false);
  });

  // v0.3.1 Cut F final fix — gemma family default 정정. gemma4 도 vision-capable hint.
  it('returns true for gemma4 family (future-proof)', () => {
    const model: OllamaModel = { name: 'gemma4-vision:e4b', details: { family: 'gemma4' } };
    expect(isVisionCapable(model)).toBe(true);
  });

  it('returns true for gemma4 in name hints (no family)', () => {
    const model: OllamaModel = { name: 'custom-gemma4:latest' };
    expect(isVisionCapable(model)).toBe(true);
  });
});

// ---------------------------------------------------------------------------
// refreshVisionCache
// ---------------------------------------------------------------------------
describe('refreshVisionCache', () => {
  function makeSettings(overrides: Partial<{
    isAiEnabled: boolean;
    setCalled: { models: string[]; at: Date } | null;
  }> = {}) {
    const setCalled: { models: string[]; at: Date } | null = null;
    const settings = {
      isAiEnabled: vi.fn().mockResolvedValue(overrides.isAiEnabled ?? true),
      setVisionCapableCache: vi.fn().mockImplementation(async () => undefined),
    };
    return settings;
  }

  it('returns ok:false with reason "ai_disabled" when AI is off', async () => {
    const settings = makeSettings({ isAiEnabled: false });
    const result = await refreshVisionCache({
      settings: settings as never,
      endpoint: 'http://localhost:11434',
    });
    expect(result).toEqual({ ok: false, reason: 'ai_disabled' });
    expect(settings.setVisionCapableCache).not.toHaveBeenCalled();
  });

  it('returns ok:false with http reason on non-ok response', async () => {
    const settings = makeSettings();
    const fetchImpl = vi.fn().mockResolvedValue({ ok: false, status: 503 });
    const result = await refreshVisionCache({
      settings: settings as never,
      endpoint: 'http://localhost:11434',
      fetchImpl: fetchImpl as never,
    });
    expect(result).toEqual({ ok: false, reason: 'tags http 503' });
  });

  it('returns ok:false with unreachable reason on fetch throw', async () => {
    const settings = makeSettings();
    const fetchImpl = vi.fn().mockRejectedValue(new Error('ECONNREFUSED'));
    const result = await refreshVisionCache({
      settings: settings as never,
      endpoint: 'http://localhost:11434',
      fetchImpl: fetchImpl as never,
    });
    expect(result.ok).toBe(false);
    if (!result.ok) expect(result.reason).toMatch(/unreachable/);
  });

  it('filters vision-capable models, persists cache, returns ok:true + models', async () => {
    const settings = makeSettings();
    const fixedNow = new Date('2026-05-09T00:00:00.000Z');
    const responseBody = {
      models: [
        { name: 'llava:13b', details: { family: 'llava' } },
        { name: 'gemma2:9b', details: { family: 'gemma' } },
        { name: 'qwen2-vl:7b' },
      ],
    };
    const fetchImpl = vi.fn().mockResolvedValue({
      ok: true,
      json: () => Promise.resolve(responseBody),
    });
    const result = await refreshVisionCache({
      settings: settings as never,
      endpoint: 'http://localhost:11434',
      fetchImpl: fetchImpl as never,
      now: () => fixedNow,
    });
    expect(result).toEqual({ ok: true, models: ['llava:13b', 'qwen2-vl:7b'] });
    expect(settings.setVisionCapableCache).toHaveBeenCalledWith(
      ['llava:13b', 'qwen2-vl:7b'],
      fixedNow
    );
  });
});
