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

vi.mock('../../lib/sql.js', () => ({ sql: vi.fn() }));
vi.mock('../../lib/permission-middleware', () => ({
  getUserFromRequest: vi.fn(),
}));

import { sql } from '../../lib/sql.js';
import { getUserFromRequest } from '../../lib/permission-middleware';
import handler from '../../pages/api/custom-games/index.js';
import itemHandler from '../../pages/api/custom-games/[id].js';

function createRes() {
  const res = {
    statusCode: 200,
    body: null,
    status(code) {
      res.statusCode = code;
      return res;
    },
    json(data) {
      res.body = data;
      return res;
    },
  };
  return res;
}

describe('/api/custom-games', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    getUserFromRequest.mockResolvedValue({ userId: 1, email: 'a@b.c', role: 'user' });
    sql.mockResolvedValue({ rows: [] });
  });

  it('requires authentication', async () => {
    getUserFromRequest.mockResolvedValue(null);
    const res = createRes();

    await handler({ method: 'GET' }, res);

    expect(res.statusCode).toBe(401);
    expect(sql).not.toHaveBeenCalled();
  });

  it('rejects games without a name', async () => {
    const res = createRes();

    await handler({ method: 'POST', body: { name: '  ' } }, res);

    expect(res.statusCode).toBe(400);
    expect(sql).not.toHaveBeenCalled();
  });

  it('rejects duplicate names for the same user', async () => {
    sql.mockResolvedValueOnce({ rows: [{ id: 9 }] }); // duplicate lookup hits
    const res = createRes();

    await handler({ method: 'POST', body: { name: 'Aetherfall' } }, res);

    expect(res.statusCode).toBe(409);
  });

  it('creates a game scoped to the user', async () => {
    sql
      .mockResolvedValueOnce({ rows: [] }) // duplicate lookup misses
      .mockResolvedValueOnce({
        rows: [{ id: 9, name: 'Aetherfall', description: null }],
      });

    const res = createRes();
    await handler(
      { method: 'POST', body: { name: 'Aetherfall', description: 'Skyborn TCG' } },
      res
    );

    expect(res.statusCode).toBe(201);
    expect(res.body.game.name).toBe('Aetherfall');
    expect(res.body.game.card_count).toBe(0);
  });
});

describe('/api/custom-games/[id]', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    getUserFromRequest.mockResolvedValue({ userId: 1, email: 'a@b.c', role: 'user' });
    sql.mockResolvedValue({ rows: [] });
  });

  it('returns 404 for another user\'s game', async () => {
    sql.mockResolvedValueOnce({ rows: [] }); // ownership lookup misses
    const res = createRes();

    await itemHandler({ method: 'GET', query: { id: '9' } }, res);

    expect(res.statusCode).toBe(404);
  });

  it('returns the game with its designs', async () => {
    sql
      .mockResolvedValueOnce({ rows: [{ id: 9, name: 'Aetherfall' }] }) // ownership
      .mockResolvedValueOnce({ rows: [{ id: 3, name: 'Stormsage' }] }); // designs

    const res = createRes();
    await itemHandler({ method: 'GET', query: { id: '9' } }, res);

    expect(res.statusCode).toBe(200);
    expect(res.body.game.name).toBe('Aetherfall');
    expect(res.body.designs).toEqual([{ id: 3, name: 'Stormsage', custom_frame: null }]);
  });

  it('rejects renames that clash with another game', async () => {
    sql
      .mockResolvedValueOnce({ rows: [{ id: 9, name: 'Old Name' }] }) // ownership
      .mockResolvedValueOnce({ rows: [{ id: 12 }] }); // clash lookup hits

    const res = createRes();
    await itemHandler(
      { method: 'PUT', query: { id: '9' }, body: { name: 'Taken' } },
      res
    );

    expect(res.statusCode).toBe(409);
  });

  it('deletes the game row', async () => {
    sql
      .mockResolvedValueOnce({ rows: [{ id: 9, name: 'Aetherfall' }] }) // ownership
      .mockResolvedValueOnce({ rows: [] }); // DELETE

    const res = createRes();
    await itemHandler({ method: 'DELETE', query: { id: '9' } }, res);

    expect(res.statusCode).toBe(200);
    expect(sql).toHaveBeenCalledTimes(2);
  });
});
