import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest';
import Fastify from 'fastify';
import { marketplaceRoutes } from './routes.js';

vi.mock('../../lib/config.js', () => ({
  config: {
    PLATFORM_SERVICE_URL: 'http://mock-platform:4003',
  },
}));

vi.mock('../../lib/product-config.js', () => ({
  PRODUCT_ID: 'clawcowork',
}));

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

function buildApp() {
  const app = Fastify({ logger: false });
  app.decorateRequest('jwtPayload', null);
  app.addHook('onRequest', async req => {
    (req as unknown as { jwtPayload: { sub: string; role: string } }).jwtPayload = {
      sub: 'user-1',
      role: 'user',
    };
  });
  app.register(marketplaceRoutes);
  return app;
}

describe('marketplace proxy routes', () => {
  let app: ReturnType<typeof Fastify>;

  beforeEach(() => {
    app = buildApp();
    mockFetch.mockReset();
  });

  afterEach(async () => {
    await app.close();
  });

  // ── Public Routes ──

  it('GET /api/marketplace proxies to platform-service public catalog', async () => {
    mockFetch.mockResolvedValue({
      ok: true,
      status: 200,
      json: async () => ({ items: [], total: 0, limit: 20, offset: 0 }),
    });

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

    expect(res.statusCode).toBe(200);
    const body = JSON.parse(res.body);
    expect(body.items).toEqual([]);
    expect(body.total).toBe(0);

    expect(mockFetch).toHaveBeenCalledOnce();
    const fetchUrl = mockFetch.mock.calls[0][0] as string;
    expect(fetchUrl).toContain('/public/marketplace?');
    expect(fetchUrl).toContain('productId=clawcowork');
    expect(fetchUrl).toContain('category=agent');
    expect(fetchUrl).toContain('limit=10');
  });

  it('GET /api/marketplace/:id proxies listing detail', async () => {
    mockFetch.mockResolvedValue({
      ok: true,
      status: 200,
      json: async () => ({ id: 'lst_123', title: 'Test Plugin' }),
    });

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

    expect(res.statusCode).toBe(200);
    expect(JSON.parse(res.body).id).toBe('lst_123');
    expect(mockFetch.mock.calls[0][0]).toContain('/public/marketplace/lst_123');
  });

  it('GET /api/marketplace/featured proxies featured listings', async () => {
    mockFetch.mockResolvedValue({
      ok: true,
      status: 200,
      json: async () => ({ items: [{ id: 'lst_f1' }] }),
    });

    const res = await app.inject({
      method: 'GET',
      url: '/api/marketplace/featured?limit=5',
    });

    expect(res.statusCode).toBe(200);
    expect(mockFetch.mock.calls[0][0]).toContain('/public/marketplace/featured');
  });

  it('GET /api/marketplace/categories proxies categories', async () => {
    mockFetch.mockResolvedValue({
      ok: true,
      status: 200,
      json: async () => ({ categories: ['agent', 'skill', 'workflow'] }),
    });

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

    expect(res.statusCode).toBe(200);
    expect(JSON.parse(res.body).categories).toEqual(['agent', 'skill', 'workflow']);
  });

  // ── Consumer Routes ──

  it('POST /api/marketplace/:id/install proxies install', async () => {
    mockFetch.mockResolvedValue({
      ok: true,
      status: 201,
      json: async () => ({ install: { id: 'inst_1' }, payload: {} }),
    });

    const res = await app.inject({
      method: 'POST',
      url: '/api/marketplace/lst_123/install',
    });

    expect(res.statusCode).toBe(200);
    expect(mockFetch.mock.calls[0][0]).toContain('/marketplace/listings/lst_123/install');
    expect(mockFetch.mock.calls[0][1].method).toBe('POST');
  });

  it('DELETE /api/marketplace/:id/install proxies uninstall', async () => {
    mockFetch.mockResolvedValue({
      ok: true,
      status: 200,
      json: async () => ({ success: true }),
    });

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

    expect(res.statusCode).toBe(200);
    expect(mockFetch.mock.calls[0][1].method).toBe('DELETE');
  });

  it('GET /api/marketplace/installs proxies my installs with productId', async () => {
    mockFetch.mockResolvedValue({
      ok: true,
      status: 200,
      json: async () => ({ items: [], total: 0 }),
    });

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

    expect(res.statusCode).toBe(200);
    const fetchUrl = mockFetch.mock.calls[0][0] as string;
    expect(fetchUrl).toContain('productId=clawcowork');
  });

  it('POST /api/marketplace/:id/vote proxies vote toggle', async () => {
    mockFetch.mockResolvedValue({
      ok: true,
      status: 200,
      json: async () => ({ voted: true, voteCount: 5 }),
    });

    const res = await app.inject({
      method: 'POST',
      url: '/api/marketplace/lst_123/vote',
    });

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

  // ── Author Routes ──

  it('POST /api/marketplace/listings proxies create listing', async () => {
    mockFetch.mockResolvedValue({
      ok: true,
      status: 201,
      json: async () => ({ id: 'lst_new', title: 'New Plugin' }),
    });

    const res = await app.inject({
      method: 'POST',
      url: '/api/marketplace/listings',
      payload: { title: 'New Plugin', description: 'A test plugin' },
    });

    expect(res.statusCode).toBe(200);
    expect(mockFetch.mock.calls[0][1].method).toBe('POST');
    expect(mockFetch.mock.calls[0][0]).toContain('/marketplace/listings');
  });

  it('GET /api/marketplace/listings/mine proxies my listings', async () => {
    mockFetch.mockResolvedValue({
      ok: true,
      status: 200,
      json: async () => ({ listings: [] }),
    });

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

    expect(res.statusCode).toBe(200);
    expect(mockFetch.mock.calls[0][0]).toContain('/marketplace/listings/mine');
  });

  // ── Auth Header Forwarding ──

  it('forwards Authorization header to platform-service for authenticated routes', async () => {
    mockFetch.mockResolvedValue({
      ok: true,
      status: 201,
      json: async () => ({ install: { id: 'inst_1' } }),
    });

    await app.inject({
      method: 'POST',
      url: '/api/marketplace/lst_123/install',
      headers: { authorization: 'Bearer test-jwt-token' },
    });

    expect(mockFetch).toHaveBeenCalledOnce();
    const fetchHeaders = mockFetch.mock.calls[0][1].headers as Record<string, string>;
    expect(fetchHeaders['authorization']).toBe('Bearer test-jwt-token');
    expect(fetchHeaders['x-product-id']).toBe('clawcowork');
  });

  it('omits Authorization header when no token present', async () => {
    mockFetch.mockResolvedValue({
      ok: true,
      status: 200,
      json: async () => ({ items: [], total: 0 }),
    });

    await app.inject({
      method: 'GET',
      url: '/api/marketplace',
    });

    const fetchHeaders = mockFetch.mock.calls[0][1].headers as Record<string, string>;
    expect(fetchHeaders['authorization']).toBeUndefined();
  });

  // ── Error Handling ──

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

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

    expect(res.statusCode).toBe(502);
    expect(JSON.parse(res.body).error).toBe('Platform-service unavailable');
  });

  it('forwards platform error status codes', async () => {
    mockFetch.mockResolvedValue({
      ok: false,
      status: 404,
      text: async () => JSON.stringify({ error: 'Listing not found' }),
    });

    const res = await app.inject({
      method: 'POST',
      url: '/api/marketplace/lst_missing/install',
    });

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

  it('returns 400 for invalid query parameters', async () => {
    const res = await app.inject({
      method: 'GET',
      url: '/api/marketplace?limit=-1',
    });

    expect(res.statusCode).toBe(400);
    expect(JSON.parse(res.body).error).toBe('Invalid query');
  });
});
