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

const { requireAuthMock, getEmployeDetailsMock, updateEmployeFeedMock, deleteEmployeFeedMock } = vi.hoisted(() => ({
  requireAuthMock: vi.fn(),
  getEmployeDetailsMock: vi.fn(),
  updateEmployeFeedMock: vi.fn(),
  deleteEmployeFeedMock: vi.fn(),
}));

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

vi.mock('../../../../src/server/employes.server', () => ({
  getEmployeDetails: getEmployeDetailsMock,
  updateEmployeFeed: updateEmployeFeedMock,
  deleteEmployeFeed: deleteEmployeFeedMock,
}));

import { GET, PUT, DELETE } from '../../../../src/app/api/employes/[cos]/route';
import { DependentRecordsError } from '../../../../src/lib/errors';

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/employes/[cos] route', () => {
  beforeEach(() => {
    vi.resetAllMocks();
    requireAuthMock.mockReturnValue(null);
  });

  it('GET returns 200 when employee exists', async () => {
    getEmployeDetailsMock.mockResolvedValue({ employe: { COS: 123, PRE: 'Alice' } });

    const res = await GET(new Request('http://localhost/api/employes/123'), makeCtx(GET, { cos: '123' }));

    expect(res.status).toBe(200);
    const body = await res.json();
    expect(body).toEqual({ message: 'Employe retrieved successfully', employe: { COS: 123, PRE: 'Alice' } });
  });

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

    expect(res.status).toBe(400);
    const body = await res.json();
    expect(body.message).toBe('Invalid COS parameter');
  });
  it('GET returns auth error when request is unauthorized', async () => {
    // simulate requireAuth returning an HTTP 401 Response
    requireAuthMock.mockReturnValue(new Response('Unauthorized', { status: 401 }));

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

    expect(res.status).toBe(401);
    expect(getEmployeDetailsMock).not.toHaveBeenCalled();
    });
  it('GET returns 404 when not found', async () => {
    getEmployeDetailsMock.mockResolvedValue({ employe: null });

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

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

  it('PUT updates and returns 200', async () => {
    updateEmployeFeedMock.mockResolvedValue({ employe: { COS: 123, PRE: 'Bob' } });

    const res = await PUT(
      new Request('http://localhost/api/employes/123', { method: 'PUT', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ PRE: 'Bob' }) }),
      makeCtx(PUT, { cos: '123' }),
    );

    expect(res.status).toBe(200);
    const body = await res.json();
    expect(body).toEqual({ message: 'Employe updated successfully', employe: { COS: 123, PRE: 'Bob' } });
  });
  it('PUT returns 400 when cos param is invalid', async () => {
    const res = await PUT(new Request('http://localhost/api/employes/abc'), makeCtx(PUT, { cos: 'abc' }));

    expect(res.status).toBe(400);
    const body = await res.json();
    expect(body.message).toBe('Invalid COS parameter');
  });
  it('PUT returns 400 for invalid JSON', async () => {
    const res = await PUT(
      new Request('http://localhost/api/employes/123', { method: 'PUT', headers: { 'content-type': 'application/json' }, body: '{' }),
      makeCtx(PUT, { cos: '123' }),
    );

    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/employes/123', { method: 'PUT', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ PRE: longString }) }),
      makeCtx(PUT, { cos: '123' }),
    );

    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 () => {
    // simulate requireAuth returning an HTTP 401 Response
    requireAuthMock.mockReturnValue(new Response('Unauthorized', { status: 401 }));

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

    expect(res.status).toBe(401);
    expect(updateEmployeFeedMock).not.toHaveBeenCalled();
    });
  it('PUT returns 404 when employee not found', async () => {
    updateEmployeFeedMock.mockResolvedValue({ employe: null });

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

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

  it('DELETE returns 200 on success', async () => {
    deleteEmployeFeedMock.mockResolvedValue({ employe: { COS: 123 } });

    const res = await DELETE(new Request('http://localhost/api/employes/123'), makeCtx(DELETE, { cos: '123' }));

    expect(res.status).toBe(200);
    const body = await res.json();
    expect(body).toEqual({ message: 'Employe deleted successfully', employe: { COS: 123 } });
  });
   it('DELETE returns 400 when cos param is invalid', async () => {
    const res = await DELETE(new Request('http://localhost/api/employes/abc'), makeCtx(DELETE, { cos: 'abc' }));

    expect(res.status).toBe(400);
    const body = await res.json();
    expect(body.message).toBe('Invalid COS parameter');
  });
  it('DELETE returns auth error when request is unauthorized', async () => {
    // simulate requireAuth returning an HTTP 401 Response
    requireAuthMock.mockReturnValue(new Response('Unauthorized', { status: 401 }));

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

    expect(res.status).toBe(401);
    expect(deleteEmployeFeedMock).not.toHaveBeenCalled();
    });
  it('DELETE returns 404 when not found', async () => {
    deleteEmployeFeedMock.mockResolvedValue({ employe: null });

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

    expect(res.status).toBe(404);
    const body = await res.json();
    expect(body.message).toBe('Employe not found');
  });
  it('DELETE returns 409 when Prisma P2003 is thrown', async () => {
    deleteEmployeFeedMock.mockRejectedValue(new DependentRecordsError());

    const res = await DELETE(new Request('http://localhost/api/employes/123'), makeCtx(DELETE, { cos: '123' }));

    expect(res.status).toBe(409);
    const body = await res.json();
    expect(body.message).toMatch(/Impossible to delete/i);
  });

  it('DELETE returns 409 when Prisma error object with code P2003 is thrown', async () => {
    deleteEmployeFeedMock.mockRejectedValue({ code: 'P2003' });

    const res = await DELETE(new Request('http://localhost/api/employes/123'), makeCtx(DELETE, { cos: '123' }));

    expect(res.status).toBe(409);
    const body = await res.json();
    expect(body.message).toMatch(/Impossible to delete/i);
  });

  it('DELETE returns 500 for unknown errors', async () => {
    deleteEmployeFeedMock.mockRejectedValue(new Error('boom'));

    const res = await DELETE(new Request('http://localhost/api/employes/123'), makeCtx(DELETE, { cos: '123' }));

    expect(res.status).toBe(500);
    const body = await res.json();
    expect(body.message).toMatch(/Internal Server Error/i);
  });
});
