import { describe, it, expect, beforeEach, vi } from 'vitest';
import { bootGateDecision, isAssetPath } from './install.js';
import { resetBootGateMemoForTests, runBootGate } from './boot-gate.js';

describe('bootGateDecision', () => {
  it('returns null when the site is configured', () => {
    expect(bootGateDecision('/', true)).toBeNull();
    expect(bootGateDecision('/post/hello', true)).toBeNull();
  });

  it('redirects unconfigured public paths to /install', () => {
    expect(bootGateDecision('/', false)).toBe('/install');
    expect(bootGateDecision('/post/hello', false)).toBe('/install');
  });

  it('allows install routes and assets through when unconfigured', () => {
    expect(bootGateDecision('/install', false)).toBeNull();
    expect(bootGateDecision('/api/install', false)).toBeNull();
    expect(bootGateDecision('/_astro/page.js', false)).toBeNull();
    expect(bootGateDecision('/favicon.ico', false)).toBeNull();
  });
});

describe('isAssetPath', () => {
  it('matches astro assets and favicon paths', () => {
    expect(isAssetPath('/_astro/chunk.js')).toBe(true);
    expect(isAssetPath('/favicon.ico')).toBe(true);
    expect(isAssetPath('/favicon/apple-touch.png')).toBe(true);
    expect(isAssetPath('/install')).toBe(false);
  });
});

describe('runBootGate', () => {
  beforeEach(() => {
    resetBootGateMemoForTests();
  });

  const ctx = (pathname: string) => ({ url: new URL(`https://cms.example${pathname}`) });

  it('passes through when configured', async () => {
    const res = await runBootGate(ctx('/'), {
      loadSiteConfigured: async () => true,
    });
    expect(res).toBeNull();
  });

  it('redirects unconfigured public requests to /install', async () => {
    const res = await runBootGate(ctx('/'), {
      loadSiteConfigured: async () => false,
    });
    expect(res?.status).toBe(302);
    expect(res?.headers.get('Location')).toBe('https://cms.example/install');
  });

  it('allows /install and /api/install when unconfigured', async () => {
    expect(
      await runBootGate(ctx('/install'), { loadSiteConfigured: async () => false }),
    ).toBeNull();
    expect(
      await runBootGate(ctx('/api/install'), { loadSiteConfigured: async () => false }),
    ).toBeNull();
  });

  it('allows asset paths when unconfigured', async () => {
    expect(
      await runBootGate(ctx('/_astro/chunk.js'), { loadSiteConfigured: async () => false }),
    ).toBeNull();
  });

  it('fail-opens when the configured read throws', async () => {
    const res = await runBootGate(ctx('/'), {
      loadSiteConfigured: async () => {
        throw new Error('db blip');
      },
    });
    expect(res).toBeNull();
  });

  it('memoizes permanent true — second configured request skips the read', async () => {
    const loadSiteConfigured = vi.fn(async () => true);
    expect(await runBootGate(ctx('/'), { loadSiteConfigured })).toBeNull();
    expect(await runBootGate(ctx('/post/hello'), { loadSiteConfigured })).toBeNull();
    expect(loadSiteConfigured).toHaveBeenCalledTimes(1);
  });

  it('never memoizes false — each unconfigured request re-reads', async () => {
    const loadSiteConfigured = vi.fn(async () => false);
    await runBootGate(ctx('/install'), { loadSiteConfigured });
    await runBootGate(ctx('/install'), { loadSiteConfigured });
    expect(loadSiteConfigured).toHaveBeenCalledTimes(2);
  });
});
