/**
 * Tests for /api/tracker/[...path] — catch-all proxy to platform-service.
 */

import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import type { NextRequest } from 'next/server';

// Mock global fetch
const mockFetch = vi.fn();
vi.stubGlobal('fetch', mockFetch);

import { GET, POST, PUT, PATCH, DELETE } from '@/app/api/tracker/[...path]/route';

function mockNextRequest(
  method: string,
  path: string,
  body?: string,
  headers?: Record<string, string>
): NextRequest {
  const _url = new URL(`http://localhost:3003/api/tracker/${path}`);
  const headerMap = new Map(Object.entries(headers || {}));
  return {
    method,
    headers: {
      get: (key: string) => headerMap.get(key.toLowerCase()) || headerMap.get(key) || null,
    },
    nextUrl: {
      searchParams: new URLSearchParams(),
    },
    text: async () => body || '',
  } as unknown as NextRequest;
}

describe('tracker proxy', () => {
  const originalEnv = { ...process.env };

  beforeEach(() => {
    vi.clearAllMocks();
    process.env.PLATFORM_API_URL = 'http://localhost:4003';
  });

  afterEach(() => {
    process.env = { ...originalEnv };
  });

  it('proxies GET requests to platform-service', async () => {
    mockFetch.mockResolvedValue({
      status: 200,
      text: async () => JSON.stringify({ items: [] }),
    });

    const req = mockNextRequest('GET', 'items');
    const params = Promise.resolve({ path: ['items'] });
    const res = await GET(req, { params });

    expect(res.status).toBe(200);
    expect(mockFetch).toHaveBeenCalledWith(
      expect.stringContaining('/api/items'),
      expect.objectContaining({ method: 'GET' })
    );
  });

  it('proxies POST requests with body', async () => {
    mockFetch.mockResolvedValue({
      status: 201,
      text: async () => JSON.stringify({ id: 'item_1' }),
    });

    const body = JSON.stringify({ title: 'New item', type: 'bug' });
    const req = mockNextRequest('POST', 'items', body);
    const params = Promise.resolve({ path: ['items'] });
    const res = await POST(req, { params });

    expect(res.status).toBe(201);
    expect(mockFetch).toHaveBeenCalledWith(
      expect.stringContaining('/api/items'),
      expect.objectContaining({ method: 'POST', body })
    );
  });

  it('forwards authorization header', async () => {
    mockFetch.mockResolvedValue({
      status: 200,
      text: async () => '[]',
    });

    const req = mockNextRequest('GET', 'items', undefined, {
      authorization: 'Bearer test-token',
    });
    const params = Promise.resolve({ path: ['items'] });
    await GET(req, { params });

    expect(mockFetch).toHaveBeenCalledWith(
      expect.any(String),
      expect.objectContaining({
        headers: expect.objectContaining({ Authorization: 'Bearer test-token' }),
      })
    );
  });

  it('forwards x-tracker-token as Bearer when no auth header', async () => {
    mockFetch.mockResolvedValue({
      status: 200,
      text: async () => '[]',
    });

    const req = mockNextRequest('GET', 'items', undefined, {
      'x-tracker-token': 'my-token',
    });
    const params = Promise.resolve({ path: ['items'] });
    await GET(req, { params });

    expect(mockFetch).toHaveBeenCalledWith(
      expect.any(String),
      expect.objectContaining({
        headers: expect.objectContaining({ Authorization: 'Bearer my-token' }),
      })
    );
  });

  it('returns 502 when platform-service is unreachable', async () => {
    mockFetch.mockRejectedValue(new Error('ECONNREFUSED'));

    const req = mockNextRequest('GET', 'items');
    const params = Promise.resolve({ path: ['items'] });
    const res = await GET(req, { params });

    expect(res.status).toBe(502);
    const data = await res.json();
    expect(data.error).toBe('Tracker service unavailable');
  });

  it('handles nested paths correctly', async () => {
    mockFetch.mockResolvedValue({
      status: 200,
      text: async () => '{}',
    });

    const req = mockNextRequest('GET', 'items/item_1/comments');
    const params = Promise.resolve({ path: ['items', 'item_1', 'comments'] });
    await GET(req, { params });

    expect(mockFetch).toHaveBeenCalledWith(
      expect.stringContaining('/api/items/item_1/comments'),
      expect.any(Object)
    );
  });

  it('proxies DELETE requests', async () => {
    mockFetch.mockResolvedValue({
      status: 200,
      text: async () => JSON.stringify({ success: true }),
    });

    const req = mockNextRequest('DELETE', 'items/item_1');
    const params = Promise.resolve({ path: ['items', 'item_1'] });
    const res = await DELETE(req, { params });

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

  it('proxies PATCH requests', async () => {
    mockFetch.mockResolvedValue({
      status: 200,
      text: async () => JSON.stringify({ id: 'item_1', status: 'done' }),
    });

    const body = JSON.stringify({ status: 'done' });
    const req = mockNextRequest('PATCH', 'items/item_1', body);
    const params = Promise.resolve({ path: ['items', 'item_1'] });
    const res = await PATCH(req, { params });

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

  it('proxies PUT requests', async () => {
    mockFetch.mockResolvedValue({
      status: 200,
      text: async () => JSON.stringify({ id: 'item_1' }),
    });

    const body = JSON.stringify({ title: 'Updated' });
    const req = mockNextRequest('PUT', 'items/item_1', body);
    const params = Promise.resolve({ path: ['items', 'item_1'] });
    const res = await PUT(req, { params });

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