// v2.6.10 Phase 3 (server wiring) — notifyCoderClose fire-and-forget helper.
//
// The guarantee under test: the helper NEVER throws (so it can't break the
// user's delete/archive path), targets the correct coder URL shape, and folds
// every failure mode (non-2xx, network error) into a `false` result.

import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { notifyCoderClose } from '../coder-notify.js';

const ORIGINAL_BOOCODER_URL = process.env.BOOCODER_URL;

describe('notifyCoderClose', () => {
  beforeEach(() => {
    delete process.env.BOOCODER_URL;
  });
  afterEach(() => {
    if (ORIGINAL_BOOCODER_URL === undefined) delete process.env.BOOCODER_URL;
    else process.env.BOOCODER_URL = ORIGINAL_BOOCODER_URL;
  });

  it('POSTs the chat close hook at the default coder origin and resolves true on 2xx', async () => {
    const fetcher = vi.fn().mockResolvedValue(new Response(null, { status: 200 }));
    const ok = await notifyCoderClose('chat', 'chat-123', undefined, fetcher as unknown as typeof fetch);
    expect(ok).toBe(true);
    expect(fetcher).toHaveBeenCalledTimes(1);
    const [url, init] = fetcher.mock.calls[0]!;
    expect(url).toBe('http://boocoder:3000/api/chats/chat-123/close');
    expect(init).toEqual({ method: 'POST' });
  });

  it('POSTs the session close hook with the sessions segment', async () => {
    const fetcher = vi.fn().mockResolvedValue(new Response(null, { status: 200 }));
    const ok = await notifyCoderClose('session', 'sess-abc', undefined, fetcher as unknown as typeof fetch);
    expect(ok).toBe(true);
    expect(fetcher.mock.calls[0]![0]).toBe('http://boocoder:3000/api/sessions/sess-abc/close');
  });

  it('honors BOOCODER_URL for the origin', async () => {
    process.env.BOOCODER_URL = 'http://100.114.205.53:9502';
    const fetcher = vi.fn().mockResolvedValue(new Response(null, { status: 200 }));
    await notifyCoderClose('chat', 'c1', undefined, fetcher as unknown as typeof fetch);
    expect(fetcher.mock.calls[0]![0]).toBe('http://100.114.205.53:9502/api/chats/c1/close');
  });

  it('resolves false on a non-2xx response (does not throw)', async () => {
    const fetcher = vi.fn().mockResolvedValue(new Response(null, { status: 500 }));
    const log = { debug: vi.fn() };
    const ok = await notifyCoderClose('chat', 'c1', log, fetcher as unknown as typeof fetch);
    expect(ok).toBe(false);
    expect(log.debug).toHaveBeenCalledTimes(1);
  });

  it('resolves false on a network error (coder unreachable) — never rejects', async () => {
    const fetcher = vi.fn().mockRejectedValue(new Error('ECONNREFUSED'));
    const log = { debug: vi.fn() };
    const ok = await notifyCoderClose('session', 's1', log, fetcher as unknown as typeof fetch);
    expect(ok).toBe(false);
    expect(log.debug).toHaveBeenCalledTimes(1);
  });

  it('does not require a logger', async () => {
    const fetcher = vi.fn().mockRejectedValue(new Error('boom'));
    await expect(
      notifyCoderClose('chat', 'c1', undefined, fetcher as unknown as typeof fetch),
    ).resolves.toBe(false);
  });
});
