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

vi.mock('../../lib/object-storage.js', () => ({
  put: vi.fn(),
  del: vi.fn(),
}));
vi.mock('../../lib/sql.js', () => ({ sql: vi.fn() }));
vi.mock('../../lib/permission-middleware', () => ({
  getUserFromRequest: vi.fn(),
}));
vi.mock('../../lib/rate-limit.js', () => ({
  checkUploadRateLimit: vi.fn(),
}));

import { put, del } from '../../lib/object-storage.js';
import { sql } from '../../lib/sql.js';
import { getUserFromRequest } from '../../lib/permission-middleware';
import { checkUploadRateLimit } from '../../lib/rate-limit.js';
import handler from '../../pages/api/custom-symbols/index.js';

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

/** Build a fake multipart request whose body carries the given parts. */
function multipartRequest(fields) {
  const boundary = 'testboundary';
  const body =
    fields.map((f) => `--${boundary}\r\n${f}\r\n`).join('') + `--${boundary}--\r\n`;
  return {
    method: 'POST',
    headers: { 'content-type': `multipart/form-data; boundary=${boundary}` },
    on(event, cb) {
      if (event === 'data') cb(Buffer.from(body));
      if (event === 'end') cb();
    },
  };
}

function filePart(name, filename, type) {
  return `Content-Disposition: form-data; name="${name}"; filename="${filename}"\r\nContent-Type: ${type}\r\n\r\nBINARYDATA`;
}

function textPart(name, value) {
  return `Content-Disposition: form-data; name="${name}"\r\n\r\n${value}`;
}

describe('/api/custom-symbols', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    getUserFromRequest.mockResolvedValue({ userId: 1, email: 'a@b.c', role: 'user' });
    checkUploadRateLimit.mockResolvedValue({ allowed: true, reset: Date.now() + 60000 });
    sql.mockResolvedValue({ rows: [] });
    put.mockResolvedValue({ url: 'https://cdn.example.com/symbols/x.png' });
  });

  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 invalid symbol codes', async () => {
    const req = multipartRequest([
      filePart('image', 'f.png', 'image/png'),
      textPart('code', 'bad code!'),
    ]);
    const res = createRes();

    await handler(req, res);

    expect(res.statusCode).toBe(400);
    expect(res.body.error).toContain('1-10 letters');
  });

  it('rejects unsupported file types', async () => {
    const req = multipartRequest([
      filePart('image', 'f.gif', 'image/gif'),
      textPart('code', 'F'),
    ]);
    const res = createRes();

    await handler(req, res);

    expect(res.statusCode).toBe(400);
    expect(res.body.error).toContain('PNG, WebP, or SVG');
  });

  it('uploads a symbol and returns its URL', async () => {
    sql
      .mockResolvedValueOnce({ rows: [] }) // existing lookup misses
      .mockResolvedValueOnce({
        rows: [{ id: 7, code: 'F', image_url: 'https://cdn.example.com/symbols/x.png' }],
      });

    const req = multipartRequest([
      filePart('image', 'fire.png', 'image/png'),
      textPart('code', 'F'),
    ]);
    const res = createRes();

    await handler(req, res);

    expect(res.statusCode).toBe(201);
    expect(res.body.symbol.code).toBe('F');
    expect(put).toHaveBeenCalledTimes(1);
    expect(put.mock.calls[0][0]).toContain('symbols/1-f-');
  });

  it('replaces an existing symbol with the same code', async () => {
    sql
      .mockResolvedValueOnce({
        rows: [{ id: 6, image_url: 'https://cdn.example.com/symbols/old.png' }],
      }) // existing lookup hits
      .mockResolvedValueOnce({
        rows: [{ id: 6, code: 'F', image_url: 'https://cdn.example.com/symbols/x.png' }],
      });

    const req = multipartRequest([
      filePart('image', 'fire.png', 'image/png'),
      textPart('code', 'F'),
    ]);
    const res = createRes();

    await handler(req, res);

    expect(res.statusCode).toBe(200);
    expect(del).toHaveBeenCalledWith('https://cdn.example.com/symbols/old.png');
  });
});
