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

const { requireAuthMock, getClassificationByIdMock, updateClassificationMock, deleteClassificationMock } = vi.hoisted(() => ({
  requireAuthMock: vi.fn(),
  getClassificationByIdMock: vi.fn(),
  updateClassificationMock: vi.fn(),
  deleteClassificationMock: vi.fn(),
}));

vi.mock('@/lib/requireAuth', () => ({
  requireAuth: requireAuthMock,
}));

vi.mock('@/services/reference-lookups.service', () => ({
  getClassificationById: getClassificationByIdMock,
  updateClassification: updateClassificationMock,
  deleteClassification: deleteClassificationMock,
}));

import { GET, PUT, PATCH, DELETE } from '../../../../src/app/api/classifications/[id]/route';

const makeCtx = <T extends (...args: any[]) => any>(fn: T, params: Record<string, string>) => ({ params: Promise.resolve(params) } as unknown as Parameters<T>[1]);

describe('Unit — /api/classification/[id] route', () => {
  beforeEach(() => {
    vi.resetAllMocks();
    requireAuthMock.mockReturnValue(null);
  });

  it('GET returns 200 when classification exists', async () => {
    getClassificationByIdMock.mockResolvedValue({ id: 1, classification: 'Test' });

    const res = await GET(new Request('http://localhost/api/classifications/1'), makeCtx(GET, { id: '1' }));

    expect(res.status).toBe(200);
    const body = await res.json();
    expect(body).toEqual({ message: 'Classification retrieved successfully', classification: { id: 1, classification: 'Test' } });
  });

  it('GET returns 400 when id param is invalid', async () => {
    const res = await GET(new Request('http://localhost/api/classifications/abc'), makeCtx(GET, { id: 'abc' }));

    expect(res.status).toBe(400);
    const body = await res.json();
    expect(body.message).toBe('Invalid id parameter');
  });

  it('GET returns auth error when request is unauthorized', async () => {
    requireAuthMock.mockReturnValue(new Response('Unauthorized', { status: 401 }));

    const res = await GET(new Request('http://localhost/api/classifications/1'), makeCtx(GET, { id: '1' }));

    expect(res.status).toBe(401);
    expect(getClassificationByIdMock).not.toHaveBeenCalled();
  });

  it('GET returns 404 when not found', async () => {
    getClassificationByIdMock.mockResolvedValue(null);

    const res = await GET(new Request('http://localhost/api/classifications/999'), makeCtx(GET, { id: '999' }));

    expect(res.status).toBe(404);
    const body = await res.json();
    expect(body.message).toBe('Classification not found');
  });

  it('PUT updates and returns 200', async () => {
    updateClassificationMock.mockResolvedValue({ id: 1, classification: 'Updated' });

    const res = await PUT(
      new Request('http://localhost/api/classifications/1', { method: 'PUT', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ classification: 'Updated' }) }),
      makeCtx(PUT, { id: '1' }),
    );

    expect(res.status).toBe(200);
    const body = await res.json();
    expect(body).toEqual({
      message: 'Classification updated successfully',
      classification: {
        id: 1, classification: 'Updated'

      }
    });
  });

  it('PUT returns 400 when id param is invalid', async () => {
    const res = await PUT(new Request('http://localhost/api/classifications/abc'), makeCtx(PUT, { id: 'abc' }));

    expect(res.status).toBe(400);
    const body = await res.json();
    expect(body.message).toBe('Invalid id parameter');
  });

  it('PUT returns 400 for invalid JSON', async () => {
    const res = await PUT(
      new Request('http://localhost/api/classifications/1', { method: 'PUT', headers: { 'content-type': 'application/json' }, body: '{' }),
      makeCtx(PUT, { id: '1' }),
    );

    expect(res.status).toBe(400);
    const body = await res.json();
    expect(body.message).toBe('Invalid JSON body');
  });

  it('PUT returns 400 when validation fails', async () => {
    const longString = 'x'.repeat(1000);

    const res = await PUT(
      new Request('http://localhost/api/classifications/1', { method: 'PUT', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ classification: longString }) }),
      makeCtx(PUT, { id: '1' }),
    );

    expect(res.status).toBe(400);
    const body = await res.json();
    expect(body.message).toBe('Invalid body parameters');
  });

  it('PUT returns auth error when request is unauthorized', async () => {
    requireAuthMock.mockReturnValue(new Response('Unauthorized', { status: 401 }));

    const res = await PUT(new Request('http://localhost/api/classifications/1'), makeCtx(PUT, { id: '1' }));

    expect(res.status).toBe(401);
    expect(updateClassificationMock).not.toHaveBeenCalled();
  });

  it('PUT returns 404 when not found', async () => {
    updateClassificationMock.mockRejectedValue(new Error('not found'));

    const res = await PUT(
      new Request('http://localhost/api/classifications/999', { method: 'PUT', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ classification: 'X' }) }),
      makeCtx(PUT, { id: '999' }),
    );

    expect(res.status).toBe(404);
    const body = await res.json();
    expect(body.message).toBe('Classification not found');
  });

  it('PATCH updates partially and returns 200', async () => {
    updateClassificationMock.mockResolvedValue({ id: 1, classification: 'Patched' });

    const res = await PATCH(
      new Request('http://localhost/api/classifications/1', { method: 'PATCH', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ classification: 'New' }) }),
      makeCtx(PATCH, { id: '1' }),
    );

    expect(res.status).toBe(200);
    const body = await res.json();
    expect(body).toEqual({ message: 'Classification patched successfully', classification: { id: 1, classification: 'Patched' } });
  });

  it('PATCH returns 404 when not found', async () => {
    updateClassificationMock.mockRejectedValue(new Error('not found'));

    const res = await PATCH(
      new Request('http://localhost/api/classifications/999', { method: 'PATCH', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ classification: 'X' }) }),
      makeCtx(PATCH, { id: '999' }),
    );

    expect(res.status).toBe(404);
    const body = await res.json();
    expect(body.message).toBe('Classification not found');
  });

  it('PATCH returns 400 when id param is invalid', async () => {
    const res = await PATCH(new Request('http://localhost/api/classifications/abc'), makeCtx(PATCH, { id: 'abc' }));

    expect(res.status).toBe(400);
    const body = await res.json();
    expect(body.message).toBe('Invalid id parameter');
  });

  it('PATCH returns 400 for invalid JSON', async () => {
    const res = await PATCH(
      new Request('http://localhost/api/classifications/1', { method: 'PATCH', headers: { 'content-type': 'application/json' }, body: '{' }),
      makeCtx(PATCH, { id: '1' }),
    );

    expect(res.status).toBe(400);
    const body = await res.json();
    expect(body.message).toBe('Invalid JSON body');
  });

  it('PATCH returns auth error when request is unauthorized', async () => {
    requireAuthMock.mockReturnValue(new Response('Unauthorized', { status: 401 }));

    const res = await PATCH(new Request('http://localhost/api/classifications/1'), makeCtx(PATCH, { id: '1' }));

    expect(res.status).toBe(401);
    expect(updateClassificationMock).not.toHaveBeenCalled();
  });

  it('PATCH returns 400 when validation fails', async () => {
    const longString = 'x'.repeat(1000);

    const res = await PATCH(
      new Request('http://localhost/api/classifications/1', { method: 'PATCH', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ classification: longString }) }),
      makeCtx(PATCH, { id: '1' }),
    );

    expect(res.status).toBe(400);
    const body = await res.json();
    expect(body.message).toBe('Invalid body parameters');
  });

  it('DELETE returns 200 on success', async () => {
    deleteClassificationMock.mockResolvedValue({ id: 1 });

    const res = await DELETE(new Request('http://localhost/api/classifications/1'), makeCtx(DELETE, { id: '1' }));

    expect(res.status).toBe(200);
    const body = await res.json();
    expect(body).toEqual({
       message: 'Classification deleted successfully',
      classification: { id: 1 } });
  });

  it('DELETE returns 400 when id param is invalid', async () => {
    const res = await DELETE(new Request('http://localhost/api/classifications/abc'), makeCtx(DELETE, { id: 'abc' }));

    expect(res.status).toBe(400);
    const body = await res.json();
    expect(body.message).toBe('Invalid id parameter');
  });

  it('DELETE returns auth error when request is unauthorized', async () => {
    requireAuthMock.mockReturnValue(new Response('Unauthorized', { status: 401 }));

    const res = await DELETE(new Request('http://localhost/api/classifications/1'), makeCtx(DELETE, { id: '1' }));

    expect(res.status).toBe(401);
    expect(deleteClassificationMock).not.toHaveBeenCalled();
  });

  it('DELETE returns 404 when not found', async () => {
    deleteClassificationMock.mockRejectedValue(new Error('not found'));

    const res = await DELETE(new Request('http://localhost/api/classifications/999'), makeCtx(DELETE, { id: '999' }));

    expect(res.status).toBe(404);
    const body = await res.json();
    expect(body.message).toBe('Classification not found');
  });
});