import { describe, it, expect, beforeEach, vi } from 'vitest';

const { getCurrentUserMock } = vi.hoisted(() => ({
  getCurrentUserMock: vi.fn(),
}));
vi.mock('@/lib/auth', async (orig) => {
  const actual = (await orig()) as Record<string, unknown>;
  return { ...actual, getCurrentUser: getCurrentUserMock };
});

import { NextRequest } from 'next/server';
import { prisma } from '@/lib/prisma';
import { POST as createConfig } from '@/app/api/ai/configs/route';
import { PATCH as patchConfig } from '@/app/api/ai/configs/[id]/route';

/**
 * Base-URL hygiene on AI config writes:
 *  - Fixed-URL providers (claude/openai/gemini) must NEVER store a base URL,
 *    even if one rides along in the body (the footgun that produced a gemini
 *    config silently pointed at a custom URL the gemini provider ignores).
 *  - Custom-URL providers (incl. SparkControl) keep it. SparkControl also
 *    needs no API key.
 */

function jsonReq(url: string, body: unknown, method = 'POST'): NextRequest {
  return new NextRequest(url, {
    method,
    headers: { 'content-type': 'application/json' },
    body: JSON.stringify(body),
  } as ConstructorParameters<typeof NextRequest>[1]);
}

async function makeAdmin(email: string) {
  return prisma.user.create({
    data: { email, passwordHash: 'fake', isAdmin: true },
  });
}

beforeEach(async () => {
  await prisma.aIConfigProfile.deleteMany();
  await prisma.userPreferences.deleteMany();
  await prisma.user.deleteMany();
  getCurrentUserMock.mockReset();
});

describe('POST /api/ai/configs — base-URL hygiene', () => {
  it('drops a base URL on a fixed-URL provider (gemini) and its active mirror', async () => {
    const u = await makeAdmin('a@x.com');
    getCurrentUserMock.mockResolvedValue(u);

    const res = await createConfig(
      jsonReq('http://x/api/ai/configs', {
        provider: 'gemini',
        model: 'gemini-2.5-flash',
        baseUrl: 'http://192.168.1.72:62419/v1',
        apiKey: 'sk-real',
        setActive: true,
      }),
    );
    expect(res.status).toBe(200);
    const body = await res.json();
    expect(body.baseUrl).toBeNull();

    const profile = await prisma.aIConfigProfile.findFirstOrThrow({
      where: { userId: u.id },
    });
    expect(profile.baseUrl).toBeNull();

    // The legacy mirror in UserPreferences must also be clean.
    const prefs = await prisma.userPreferences.findUnique({ where: { userId: u.id } });
    expect(prefs?.aiBaseUrl).toBeNull();
  });

  it('keeps the base URL for SparkControl and needs no API key', async () => {
    const u = await makeAdmin('b@x.com');
    getCurrentUserMock.mockResolvedValue(u);

    const res = await createConfig(
      jsonReq('http://x/api/ai/configs', {
        provider: 'sparkcontrol',
        model: 'RedHatAI/Qwen3.6-35B-A3B-NVFP4',
        baseUrl: 'http://spark-control.startos:9999/v1',
        setActive: true,
      }),
    );
    expect(res.status).toBe(200);
    const body = await res.json();
    expect(body.baseUrl).toBe('http://spark-control.startos:9999/v1');
    expect(body.keyConfigured).toBe(false);

    const profile = await prisma.aIConfigProfile.findFirstOrThrow({
      where: { userId: u.id },
    });
    expect(profile.baseUrl).toBe('http://spark-control.startos:9999/v1');
    expect(profile.apiKey).toBeNull();

    const prefs = await prisma.userPreferences.findUnique({ where: { userId: u.id } });
    expect(prefs?.aiBaseUrl).toBe('http://spark-control.startos:9999/v1');
  });

  it('rejects a non-admin creating a SparkControl config and names it in the error', async () => {
    const u = await prisma.user.create({
      data: { email: 'nonadmin@x.com', passwordHash: 'fake', isAdmin: false },
    });
    getCurrentUserMock.mockResolvedValue(u);

    const res = await createConfig(
      jsonReq('http://x/api/ai/configs', {
        provider: 'sparkcontrol',
        model: 'some-model',
        baseUrl: 'http://spark-control.startos:9999/v1',
      }),
    );
    expect(res.status).toBe(403);
    const body = await res.json();
    expect(body.error).toContain('SparkControl');
  });
});

describe('PATCH /api/ai/configs/[id] — base-URL hygiene', () => {
  it('refuses to attach a base URL to a fixed-URL provider on edit', async () => {
    const u = await makeAdmin('c@x.com');
    getCurrentUserMock.mockResolvedValue(u);

    const created = await prisma.aIConfigProfile.create({
      data: { userId: u.id, name: 'g', provider: 'gemini', model: 'gemini-2.5-flash' },
    });

    const res = await patchConfig(
      jsonReq(`http://x/api/ai/configs/${created.id}`, {
        baseUrl: 'http://192.168.1.72:62419/v1',
      }, 'PATCH'),
      { params: Promise.resolve({ id: created.id }) },
    );
    expect(res.status).toBe(200);

    const after = await prisma.aIConfigProfile.findFirstOrThrow({ where: { id: created.id } });
    expect(after.baseUrl).toBeNull();
  });

  it('cleans the active-config mirror when stripping a base URL on edit', async () => {
    const u = await makeAdmin('d@x.com');
    getCurrentUserMock.mockResolvedValue(u);

    // Create + activate a gemini config (mirrors into UserPreferences).
    const createRes = await createConfig(
      jsonReq('http://x/api/ai/configs', {
        provider: 'gemini',
        model: 'gemini-2.5-flash',
        apiKey: 'sk-real',
        setActive: true,
      }),
    );
    const { id } = await createRes.json();

    const res = await patchConfig(
      jsonReq(`http://x/api/ai/configs/${id}`, {
        baseUrl: 'http://192.168.1.72:62419/v1',
      }, 'PATCH'),
      { params: Promise.resolve({ id }) },
    );
    expect(res.status).toBe(200);

    const after = await prisma.aIConfigProfile.findFirstOrThrow({ where: { id } });
    expect(after.baseUrl).toBeNull();
    // The active mirror in UserPreferences must be clean too.
    const prefs = await prisma.userPreferences.findUnique({ where: { userId: u.id } });
    expect(prefs?.aiBaseUrl).toBeNull();
  });
});
