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

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

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

const itemRepoMock = {
  getById: vi.fn(),
  incrementCommentCount: vi.fn(),
};

const authMock = {
  extractAuth: vi.fn(),
};

vi.mock('./repository.js', () => commentRepoMock);
vi.mock('../items/repository.js', () => itemRepoMock);
vi.mock('../../lib/auth.js', () => authMock);

const baseItem = {
  id: 'item_1',
  productId: 'lysnrai',
  title: 'Feature request',
};

const baseComment = {
  id: 'cmt_1',
  itemId: 'item_1',
  productId: 'lysnrai',
  authorId: 'user_1',
  authorEmail: 'user1@example.com',
  body: 'Looks good',
  createdAt: '2026-02-16T00:00:00Z',
  updatedAt: '2026-02-16T00:00:00Z',
};

describe('commentRoutes', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    authMock.extractAuth.mockResolvedValue({ sub: 'user_1', email: 'user1@example.com', role: 'user' });
  });

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

  it('GET /items/:itemId/comments returns list', async () => {
    itemRepoMock.getById.mockResolvedValue(baseItem);
    commentRepoMock.listByItem.mockResolvedValue([baseComment]);

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

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

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

  it('POST /items/:itemId/comments creates comment', async () => {
    itemRepoMock.getById.mockResolvedValue(baseItem);
    commentRepoMock.create.mockResolvedValue(baseComment);

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

    const res = await app.inject({
      method: 'POST',
      url: '/api/items/item_1/comments',
      payload: { body: 'Looks good' },
    });

    expect(res.statusCode).toBe(201);
    expect(itemRepoMock.incrementCommentCount).toHaveBeenCalledWith('item_1', 1);
  });

  it('POST /items/:itemId/comments returns 400 for invalid payload', async () => {
    itemRepoMock.getById.mockResolvedValue(baseItem);

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

    const res = await app.inject({
      method: 'POST',
      url: '/api/items/item_1/comments',
      payload: { body: '' },
    });

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

  it('PUT /items/:itemId/comments/:id updates comment for author', async () => {
    commentRepoMock.getById.mockResolvedValue(baseComment);
    commentRepoMock.update.mockResolvedValue({ ...baseComment, body: 'Updated' });

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

    const res = await app.inject({
      method: 'PUT',
      url: '/api/items/item_1/comments/cmt_1',
      payload: { body: 'Updated' },
    });

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

  it('PUT /items/:itemId/comments/:id returns 403 for non-author user', async () => {
    authMock.extractAuth.mockResolvedValue({ sub: 'user_2', email: 'user2@example.com', role: 'user' });
    commentRepoMock.getById.mockResolvedValue(baseComment);

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

    const res = await app.inject({
      method: 'PUT',
      url: '/api/items/item_1/comments/cmt_1',
      payload: { body: 'Updated' },
    });

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

  it('DELETE /items/:itemId/comments/:id deletes for author', async () => {
    commentRepoMock.getById.mockResolvedValue(baseComment);
    commentRepoMock.remove.mockResolvedValue(undefined);

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

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

    expect(res.statusCode).toBe(200);
    const data = JSON.parse(res.body);
    expect(data.success).toBe(true);
    expect(itemRepoMock.incrementCommentCount).toHaveBeenCalledWith('item_1', -1);
  });

  it('DELETE /items/:itemId/comments/:id allows admin to delete', async () => {
    authMock.extractAuth.mockResolvedValue({ sub: 'admin_1', email: 'admin@example.com', role: 'admin' });
    commentRepoMock.getById.mockResolvedValue(baseComment);

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

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

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

  it('DELETE /items/:itemId/comments/:id returns 404 when comment missing', async () => {
    commentRepoMock.getById.mockResolvedValue(null);

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

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

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