import { beforeEach, describe, expect, it, vi } from 'vitest';
import type { WebhookDispatcher } from '@getmunin/core';
import { AgentConfigService } from './config.service.ts';
import type { AgentHealthRecorder } from './agent-health.service.ts';
import type {
  AgentConfigPatch,
  AgentConfigRepository,
  AgentConfigRow,
} from './config.repository.ts';

const baseRow: AgentConfigRow = {
  id: 'singleton',
  fastModel: 'anthropic/claude-haiku-4.5',
  smartModel: null,
  providerBaseUrl: 'https://provider.example/v1',
  providerApiKeySet: false,
  maxHistoryChars: 32_000,
  maxToolIterations: 8,
  debounceMs: 500,
  createdAt: new Date('2026-01-01T00:00:00Z'),
  updatedAt: new Date('2026-01-01T00:00:00Z'),
};

function makeRepo(opts: {
  before: AgentConfigRow;
  after: AgentConfigRow;
}): AgentConfigRepository & { update: ReturnType<typeof vi.fn> } {
  const update = vi.fn().mockResolvedValue(opts.after);
  return {
    resolveCurrentId: () => opts.before.id,
    resolveOrgId: (id: string) => Promise.resolve(id),
    read: vi.fn().mockResolvedValue(opts.before),
    update,
    listProvisionedIds: vi.fn().mockResolvedValue([]),
    readDecryptedProviderKey: vi.fn().mockResolvedValue(null),
  };
}

function makeWebhooks(): WebhookDispatcher & { emit: ReturnType<typeof vi.fn> } {
  const stub = { emit: vi.fn().mockResolvedValue('evt_stub') };
  return stub;
}

function makeHealthStub(): AgentHealthRecorder & {
  recordSuccess: ReturnType<typeof vi.fn>;
  recordFailure: ReturnType<typeof vi.fn>;
} {
  return {
    recordSuccess: vi.fn().mockResolvedValue({ flipped: false }),
    recordFailure: vi.fn().mockResolvedValue(undefined),
  };
}

describe('AgentConfigService', () => {
  beforeEach(() => {
    vi.stubGlobal(
      'fetch',
      vi.fn().mockResolvedValue({ ok: true, status: 200, json: () => Promise.resolve({ data: [] }) }),
    );
  });

  it('reads + serialises the row into a DTO with ISO timestamps', async () => {
    const repo = makeRepo({ before: baseRow, after: baseRow });
    const svc = new AgentConfigService(repo, makeWebhooks(), makeHealthStub());
    const dto = await svc.getForCurrentActor();
    expect(dto.id).toBe('singleton');
    expect(dto.createdAt).toBe('2026-01-01T00:00:00.000Z');
    expect(dto.updatedAt).toBe('2026-01-01T00:00:00.000Z');
  });

  it('passes the patch through to the repo verbatim', async () => {
    const repo = makeRepo({ before: baseRow, after: baseRow });
    const svc = new AgentConfigService(repo, makeWebhooks(), makeHealthStub());

    const patch: AgentConfigPatch = {
      fastModel: 'a',
      smartModel: 'b',
      providerBaseUrl: 'https://x',
      maxHistoryChars: 64_000,
    };
    await svc.upsertForCurrentActor(patch);

    expect(repo.update).toHaveBeenCalledWith('singleton', patch);
  });

  it('emits a webhook on upsert', async () => {
    const repo = makeRepo({ before: baseRow, after: baseRow });
    const webhooks = makeWebhooks();
    const svc = new AgentConfigService(repo, webhooks, makeHealthStub());

    await svc.upsertForCurrentActor({ fastModel: 'x' });

    expect(webhooks.emit).toHaveBeenCalledWith({
      type: 'agent.config.updated',
      payload: { configId: 'singleton' },
    });
  });

  it('records success when fastModel changes so a degraded agent can recover', async () => {
    const repo = makeRepo({ before: baseRow, after: baseRow });
    const health = makeHealthStub();
    const svc = new AgentConfigService(repo, makeWebhooks(), health);

    await svc.upsertForCurrentActor({ fastModel: 'anthropic/claude-sonnet-4.6' });

    expect(health.recordSuccess).toHaveBeenCalledWith('singleton');
  });

  it('records success when smartModel changes', async () => {
    const repo = makeRepo({ before: baseRow, after: baseRow });
    const health = makeHealthStub();
    const svc = new AgentConfigService(repo, makeWebhooks(), health);

    await svc.upsertForCurrentActor({ smartModel: 'anthropic/claude-opus-4.7' });

    expect(health.recordSuccess).toHaveBeenCalledWith('singleton');
  });

  it('does not record success when model patch matches the existing value', async () => {
    const repo = makeRepo({ before: baseRow, after: baseRow });
    const health = makeHealthStub();
    const svc = new AgentConfigService(repo, makeWebhooks(), health);

    await svc.upsertForCurrentActor({ fastModel: baseRow.fastModel });

    expect(health.recordSuccess).not.toHaveBeenCalled();
  });
});
