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

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

const repoMock = {
  list: vi.fn(),
  getById: vi.fn(),
  create: vi.fn(),
  update: vi.fn(),
  remove: vi.fn(),
};

const authMock = {
  extractAuth: vi.fn(async () => ({ sub: 'user_1', role: 'admin' })),
};

vi.mock('./repository.js', () => repoMock);
vi.mock('../../lib/auth.js', () => authMock);
vi.mock('../../lib/request-context.js', () => ({
  getRequestProductId: () => 'lysnrai',
}));

const baseItem = {
  id: 'trk_1',
  productId: 'lysnrai',
  type: 'feature',
  status: 'open',
  priority: 'high',
  title: 'Add better dictation UX',
  description: 'Improve flow',
  labels: ['ux'],
  assignee: null,
  reportedBy: 'user_1',
  source: 'internal',
  visibility: 'internal',
  voteCount: 1,
  commentCount: 0,
  priorityOrder: 1,
  targetRelease: null,
  createdAt: '2026-02-16T00:00:00Z',
  updatedAt: '2026-02-16T00:00:00Z',
};

async function buildApp() {
  const { itemRoutes } = await import('./routes.js');
  const app = Fastify({ logger: false });
  await app.register(itemRoutes, { prefix: '/api' });
  return app;
}

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

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

  it('GET /items/stats returns aggregates', async () => {
    repoMock.list.mockResolvedValue({ items: [baseItem], total: 1 });
    const app = await buildApp();

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

    expect(res.statusCode).toBe(200);
    const data = JSON.parse(res.body);
    expect(data.total).toBe(1);
    expect(data.byType.feature).toBe(1);
    expect(data.requestedBy).toBe('user_1');
  });

  it('GET /items returns 400 for invalid query', async () => {
    const app = await buildApp();
    const res = await app.inject({ method: 'GET', url: '/api/items?limit=0' });
    expect(res.statusCode).toBe(400);
  });

  it('GET /items returns paginated list', async () => {
    repoMock.list.mockResolvedValue({ items: [baseItem], total: 1 });
    const app = await buildApp();

    const res = await app.inject({ method: 'GET', url: '/api/items?limit=10&offset=0' });

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

  it('GET /items/:id returns 404 when missing', async () => {
    repoMock.getById.mockResolvedValue(null);
    const app = await buildApp();

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

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

  it('POST /items creates item', async () => {
    repoMock.create.mockResolvedValue(baseItem);
    const app = await buildApp();

    const res = await app.inject({
      method: 'POST',
      url: '/api/items',
      payload: {
        type: 'feature',
        priority: 'high',
        title: 'Add better dictation UX',
      },
    });

    expect(res.statusCode).toBe(201);
    expect(repoMock.create).toHaveBeenCalled();
  });

  it('PUT /items/:id updates item and priorityOrder', async () => {
    repoMock.getById.mockResolvedValue(baseItem);
    repoMock.update.mockResolvedValue({ ...baseItem, priority: 'critical', priorityOrder: 0 });
    const app = await buildApp();

    const res = await app.inject({
      method: 'PUT',
      url: '/api/items/trk_1',
      payload: { priority: 'critical', title: 'Urgent dictation fix' },
    });

    expect(res.statusCode).toBe(200);
    const data = JSON.parse(res.body);
    expect(data.priority).toBe('critical');
  });

  it('PATCH /items/:id/status updates status', async () => {
    repoMock.getById.mockResolvedValue(baseItem);
    repoMock.update.mockResolvedValue({ ...baseItem, status: 'done' });
    const app = await buildApp();

    const res = await app.inject({
      method: 'PATCH',
      url: '/api/items/trk_1/status',
      payload: { status: 'done' },
    });

    expect(res.statusCode).toBe(200);
    const data = JSON.parse(res.body);
    expect(data.status).toBe('done');
  });

  it('DELETE /items/:id deletes existing item', async () => {
    repoMock.getById.mockResolvedValue(baseItem);
    repoMock.remove.mockResolvedValue(undefined);
    const app = await buildApp();

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

    expect(res.statusCode).toBe(200);
    expect(JSON.parse(res.body).success).toBe(true);
  });
});
