/**
 * Route-level tests for public module — Fastify inject.
 */

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

const itemRepoMock = {
  list: vi.fn(),
  getById: vi.fn(),
  create: vi.fn(),
  updateVoteCount: vi.fn(),
};

const voteRepoMock = {
  create: vi.fn(),
  getByItemAndUser: vi.fn(),
  remove: vi.fn(),
  countByItem: vi.fn(),
};

vi.mock('../items/repository.js', () => itemRepoMock);
vi.mock('../votes/repository.js', () => voteRepoMock);
vi.mock('../../lib/request-context.js', () => ({
  getRequestProductId: () => 'lysnrai',
}));

const baseItem = {
  id: 'item_1',
  productId: 'lysnrai',
  type: 'feature',
  status: 'open',
  priority: 'medium',
  title: 'Improve transcription speed',
  description: 'Improve performance for long audio',
  labels: [],
  assignee: null,
  reportedBy: 'user@example.com',
  source: 'user_submitted',
  visibility: 'public',
  voteCount: 2,
  commentCount: 0,
  priorityOrder: 2,
  targetRelease: null,
  createdAt: '2026-02-16T00:00:00Z',
  updatedAt: '2026-02-16T00:00:00Z',
};

describe('publicRoutes', () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  afterEach(() => {
    vi.restoreAllMocks();
  });

  it('GET /public/roadmap returns public items', async () => {
    itemRepoMock.list.mockResolvedValue({ items: [baseItem], total: 1 });

    const { publicRoutes } = await import('./routes.js');
    const app = Fastify({ logger: false });
    await app.register(publicRoutes, { prefix: '/api' });

    const res = await app.inject({ method: 'GET', url: '/api/public/roadmap' });

    expect(res.statusCode).toBe(200);
    const data = JSON.parse(res.body);
    expect(data.items).toHaveLength(1);
    expect(itemRepoMock.list).toHaveBeenCalled();
  });

  it('GET /public/roadmap returns 400 for invalid query', async () => {
    const { publicRoutes } = await import('./routes.js');
    const app = Fastify({ logger: false });
    await app.register(publicRoutes, { prefix: '/api' });

    const res = await app.inject({
      method: 'GET',
      url: '/api/public/roadmap?status=invalid',
    });

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

  it('GET /public/roadmap/stats aggregates status/type/votes', async () => {
    itemRepoMock.list.mockResolvedValue({
      items: [
        { ...baseItem, status: 'open', type: 'feature', voteCount: 2 },
        { ...baseItem, id: 'item_2', status: 'in_progress', type: 'bug', voteCount: 3 },
      ],
      total: 2,
    });

    const { publicRoutes } = await import('./routes.js');
    const app = Fastify({ logger: false });
    await app.register(publicRoutes, { prefix: '/api' });

    const res = await app.inject({ method: 'GET', url: '/api/public/roadmap/stats' });

    expect(res.statusCode).toBe(200);
    const data = JSON.parse(res.body);
    expect(data.total).toBe(2);
    expect(data.totalVotes).toBe(5);
    expect(data.byStatus.open).toBe(1);
    expect(data.byType.feature).toBe(1);
  });

  it('GET /public/items/:id returns 404 for missing item', async () => {
    itemRepoMock.getById.mockResolvedValue(null);

    const { publicRoutes } = await import('./routes.js');
    const app = Fastify({ logger: false });
    await app.register(publicRoutes, { prefix: '/api' });

    const res = await app.inject({ method: 'GET', url: '/api/public/items/missing' });

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

  it('POST /public/submit creates tracker item and auto-vote', async () => {
    itemRepoMock.create.mockResolvedValue(baseItem);
    voteRepoMock.create.mockResolvedValue({ id: 'vote_1' });

    const { publicRoutes } = await import('./routes.js');
    const app = Fastify({ logger: false });
    await app.register(publicRoutes, { prefix: '/api' });

    const res = await app.inject({
      method: 'POST',
      url: '/api/public/submit',
      payload: {
        type: 'feature',
        priority: 'medium',
        title: 'Add hotkey customization',
        description: 'Need remap support',
        email: 'submitter@example.com',
        name: 'Submitter',
      },
    });

    expect(res.statusCode).toBe(201);
    const data = JSON.parse(res.body);
    expect(data).toHaveProperty('id');
    expect(voteRepoMock.create).toHaveBeenCalled();
  });

  it('POST /public/items/:id/vote toggles off when vote exists', async () => {
    itemRepoMock.getById.mockResolvedValue(baseItem);
    voteRepoMock.getByItemAndUser.mockResolvedValue({ id: 'vote_1' });
    voteRepoMock.countByItem.mockResolvedValue(1);

    const { publicRoutes } = await import('./routes.js');
    const app = Fastify({ logger: false });
    await app.register(publicRoutes, { prefix: '/api' });

    const res = await app.inject({
      method: 'POST',
      url: '/api/public/items/item_1/vote',
      payload: { email: 'user@example.com' },
    });

    expect(res.statusCode).toBe(200);
    const data = JSON.parse(res.body);
    expect(data.voted).toBe(false);
    expect(voteRepoMock.remove).toHaveBeenCalledWith('vote_1');
  });

  it('POST /public/items/:id/vote toggles on when vote does not exist', async () => {
    itemRepoMock.getById.mockResolvedValue(baseItem);
    voteRepoMock.getByItemAndUser.mockResolvedValue(null);
    voteRepoMock.countByItem.mockResolvedValue(3);

    const { publicRoutes } = await import('./routes.js');
    const app = Fastify({ logger: false });
    await app.register(publicRoutes, { prefix: '/api' });

    const res = await app.inject({
      method: 'POST',
      url: '/api/public/items/item_1/vote',
      payload: { email: 'user@example.com' },
    });

    expect(res.statusCode).toBe(200);
    const data = JSON.parse(res.body);
    expect(data.voted).toBe(true);
    expect(voteRepoMock.create).toHaveBeenCalled();
  });
});
