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 };
});
vi.mock('next/cache', () => ({ revalidatePath: vi.fn() }));

import { NextRequest } from 'next/server';
import { prisma } from '@/lib/prisma';
import {
  GET as listTemplates,
  POST as createTemplate,
} from '@/app/api/ai/templates/route';
import {
  PATCH as patchTemplate,
  DELETE as deleteTemplate,
} from '@/app/api/ai/templates/[id]/route';
import { GET as getConfig, POST as setConfig } from '@/app/api/ai/config/route';

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

beforeEach(async () => {
  await prisma.session.deleteMany();
  await prisma.aIGeneration.deleteMany();
  await prisma.aIPromptTemplate.deleteMany();
  await prisma.userPreferences.deleteMany();
  await prisma.user.deleteMany();
  await prisma.instanceSettings.deleteMany();
  getCurrentUserMock.mockReset();
});

describe('GET /api/ai/templates', () => {
  it('returns built-ins + this user\'s own; not other users\' templates', async () => {
    const me = await prisma.user.create({
      data: { email: 'me@x', passwordHash: 'fake' },
    });
    const other = await prisma.user.create({
      data: { email: 'other@x', passwordHash: 'fake' },
    });
    await prisma.aIPromptTemplate.create({
      data: {
        userId: null,
        name: 'Built-in 1',
        systemPrompt: 's',
        userPromptTemplate: 'u',
        isBuiltIn: true,
      },
    });
    await prisma.aIPromptTemplate.create({
      data: {
        userId: me.id,
        name: 'My template',
        systemPrompt: 's',
        userPromptTemplate: 'u',
        isBuiltIn: false,
      },
    });
    await prisma.aIPromptTemplate.create({
      data: {
        userId: other.id,
        name: "Other user's template",
        systemPrompt: 's',
        userPromptTemplate: 'u',
        isBuiltIn: false,
      },
    });

    getCurrentUserMock.mockResolvedValue(me);
    const list = await (await listTemplates()).json();
    const names = list.map((t: { name: string }) => t.name).sort();
    expect(names).toEqual(['Built-in 1', 'My template']);
  });
});

describe('POST /api/ai/templates', () => {
  it('creates a user-owned template', async () => {
    const me = await prisma.user.create({
      data: { email: 'me@x', passwordHash: 'fake' },
    });
    getCurrentUserMock.mockResolvedValue(me);
    const res = await createTemplate(
      jsonReq('http://x/api/ai/templates', {
        name: 'My new',
        systemPrompt: 'be a coach',
        userPromptTemplate: '{{userInput}}',
      }),
    );
    expect(res.status).toBe(201);
    const tpl = await res.json();
    expect(tpl.userId).toBe(me.id);
    expect(tpl.isBuiltIn).toBe(false);
  });
});

describe('PATCH /api/ai/templates/[id]', () => {
  it('rejects edits to built-ins by non-admin', async () => {
    const me = await prisma.user.create({
      data: { email: 'me@x', passwordHash: 'fake', isAdmin: false },
    });
    const builtin = await prisma.aIPromptTemplate.create({
      data: {
        userId: null,
        name: 'Built-in',
        systemPrompt: 's',
        userPromptTemplate: 'u',
        isBuiltIn: true,
      },
    });
    getCurrentUserMock.mockResolvedValue(me);
    const res = await patchTemplate(
      jsonReq(
        `http://x/api/ai/templates/${builtin.id}`,
        { name: 'tampered' },
        'PATCH',
      ),
      { params: { id: builtin.id } },
    );
    expect(res.status).toBe(403);
  });

  it('allows edits to built-ins by admin', async () => {
    const admin = await prisma.user.create({
      data: { email: 'admin@x', passwordHash: 'fake', isAdmin: true },
    });
    const builtin = await prisma.aIPromptTemplate.create({
      data: {
        userId: null,
        name: 'Built-in',
        systemPrompt: 's',
        userPromptTemplate: 'u',
        isBuiltIn: true,
      },
    });
    getCurrentUserMock.mockResolvedValue(admin);
    const res = await patchTemplate(
      jsonReq(
        `http://x/api/ai/templates/${builtin.id}`,
        { name: 'admin-edited' },
        'PATCH',
      ),
      { params: { id: builtin.id } },
    );
    expect(res.status).toBe(200);
    const updated = await prisma.aIPromptTemplate.findUnique({
      where: { id: builtin.id },
    });
    expect(updated?.name).toBe('admin-edited');
  });

  it('rejects edits to another user\'s template', async () => {
    const alice = await prisma.user.create({
      data: { email: 'alice@x', passwordHash: 'fake' },
    });
    const aliceTpl = await prisma.aIPromptTemplate.create({
      data: {
        userId: alice.id,
        name: 'Alice template',
        systemPrompt: 's',
        userPromptTemplate: 'u',
      },
    });
    const bob = await prisma.user.create({
      data: { email: 'bob@x', passwordHash: 'fake' },
    });
    getCurrentUserMock.mockResolvedValue(bob);
    const res = await patchTemplate(
      jsonReq(
        `http://x/api/ai/templates/${aliceTpl.id}`,
        { name: 'bob hacks' },
        'PATCH',
      ),
      { params: { id: aliceTpl.id } },
    );
    expect(res.status).toBe(403);
  });
});

describe('DELETE /api/ai/templates/[id]', () => {
  it('lets user delete their own template', async () => {
    const me = await prisma.user.create({
      data: { email: 'me@x', passwordHash: 'fake' },
    });
    const tpl = await prisma.aIPromptTemplate.create({
      data: {
        userId: me.id,
        name: 'X',
        systemPrompt: 's',
        userPromptTemplate: 'u',
      },
    });
    getCurrentUserMock.mockResolvedValue(me);
    const res = await deleteTemplate(
      jsonReq(`http://x/api/ai/templates/${tpl.id}`, undefined, 'DELETE'),
      { params: { id: tpl.id } },
    );
    expect(res.status).toBe(200);
    expect(await prisma.aIPromptTemplate.count()).toBe(0);
  });
});

describe('/api/ai/config', () => {
  it('GET returns aiKeyConfigured flag, never the plaintext key', async () => {
    const me = await prisma.user.create({
      data: { email: 'me@x', passwordHash: 'fake' },
    });
    await prisma.userPreferences.create({
      data: {
        userId: me.id,
        aiProvider: 'claude',
        aiModel: 'claude-sonnet-4-5',
        aiApiKey: 'sk-ant-secret',
      },
    });
    getCurrentUserMock.mockResolvedValue(me);
    const body = await (await getConfig()).json();
    expect(body.aiProvider).toBe('claude');
    expect(body.aiModel).toBe('claude-sonnet-4-5');
    expect(body.aiKeyConfigured).toBe(true);
    expect(JSON.stringify(body)).not.toContain('sk-ant-secret');
  });

  it('POST persists provider config', async () => {
    const me = await prisma.user.create({
      data: { email: 'me@x', passwordHash: 'fake' },
    });
    getCurrentUserMock.mockResolvedValue(me);
    const res = await setConfig(
      jsonReq('http://x/api/ai/config', {
        aiProvider: 'ollama',
        aiModel: 'llama3.1:8b',
        aiBaseUrl: 'http://ollama.embassy:11434',
        aiApiKey: null,
      }),
    );
    expect(res.status).toBe(200);
    const prefs = await prisma.userPreferences.findUnique({
      where: { userId: me.id },
    });
    expect(prefs?.aiProvider).toBe('ollama');
    expect(prefs?.aiBaseUrl).toBe('http://ollama.embassy:11434');
    expect(prefs?.aiApiKey).toBeNull();
  });

  it('POST validates provider enum', async () => {
    const me = await prisma.user.create({
      data: { email: 'me@x', passwordHash: 'fake' },
    });
    getCurrentUserMock.mockResolvedValue(me);
    const res = await setConfig(
      jsonReq('http://x/api/ai/config', { aiProvider: 'made-up-provider' }),
    );
    expect(res.status).toBe(400);
  });
});
