import { describe, it, expect, vi, afterEach } from 'vitest';
import { createMarketplaceClient } from './client.js';
import type {
  MarketplaceListingDoc,
  MarketplaceReviewDoc,
  MarketplaceInstallDoc,
} from './types.js';

const baseConfig = {
  baseUrl: 'http://localhost:4003/api',
  productId: 'testapp',
  getAccessToken: () => 'test-token',
};

function mockListing(overrides?: Partial<MarketplaceListingDoc>): MarketplaceListingDoc {
  return {
    id: 'lst_1',
    productId: 'testapp',
    templateType: 'fasting_protocol',
    authorId: 'user-1',
    authorName: 'Alice',
    title: '16:8 Protocol',
    shortDescription: 'Classic intermittent fasting',
    description: 'A detailed 16:8 fasting protocol.',
    tags: ['fasting', 'beginner'],
    category: 'protocols',
    payload: { hours: 16, breakHours: 8 },
    pricingModel: 'free',
    priceInCents: 0,
    certificationStatus: 'approved',
    installCount: 100,
    reviewCount: 10,
    averageRating: 4.5,
    visibility: 'public',
    featured: false,
    version: '1.0.0',
    createdAt: '2026-01-01T00:00:00Z',
    updatedAt: '2026-01-01T00:00:00Z',
    ...overrides,
  };
}

function mockReview(overrides?: Partial<MarketplaceReviewDoc>): MarketplaceReviewDoc {
  return {
    id: 'rev_1',
    listingId: 'lst_1',
    productId: 'testapp',
    authorId: 'user-2',
    rating: 5,
    title: 'Great protocol!',
    body: 'Really works well for me.',
    verified: true,
    createdAt: '2026-01-01T00:00:00Z',
    ...overrides,
  };
}

function mockInstall(overrides?: Partial<MarketplaceInstallDoc>): MarketplaceInstallDoc {
  return {
    id: 'inst_1',
    listingId: 'lst_1',
    productId: 'testapp',
    userId: 'user-2',
    version: '1.0.0',
    installedAt: '2026-01-01T00:00:00Z',
    uninstalledAt: null,
    ...overrides,
  };
}

describe('createMarketplaceClient', () => {
  afterEach(() => {
    vi.restoreAllMocks();
  });

  it('should list listings', async () => {
    vi.stubGlobal(
      'fetch',
      vi.fn().mockResolvedValue({
        ok: true,
        json: () => Promise.resolve({ listings: [mockListing()], total: 1 }),
      })
    );
    const client = createMarketplaceClient(baseConfig);
    const result = await client.listListings({ category: 'protocols' });
    expect(result.listings).toHaveLength(1);
    expect(result.total).toBe(1);

    const fetchMock = globalThis.fetch as ReturnType<typeof vi.fn>;
    const url = fetchMock.mock.calls[0][0] as string;
    expect(url).toContain('category=protocols');
  });

  it('should get a listing by id', async () => {
    vi.stubGlobal(
      'fetch',
      vi.fn().mockResolvedValue({
        ok: true,
        json: () => Promise.resolve(mockListing()),
      })
    );
    const client = createMarketplaceClient(baseConfig);
    const result = await client.getListing('lst_1');
    expect(result.title).toBe('16:8 Protocol');
  });

  it('should create a listing', async () => {
    vi.stubGlobal(
      'fetch',
      vi.fn().mockResolvedValue({
        ok: true,
        json: () => Promise.resolve(mockListing()),
      })
    );
    const client = createMarketplaceClient(baseConfig);
    const result = await client.createListing({
      templateType: 'fasting_protocol',
      title: '16:8 Protocol',
      shortDescription: 'Classic',
      description: 'Detailed',
      category: 'protocols',
      payload: { hours: 16 },
    });
    expect(result.id).toBe('lst_1');

    const fetchMock = globalThis.fetch as ReturnType<typeof vi.fn>;
    expect(fetchMock).toHaveBeenCalledWith(
      expect.stringContaining('/marketplace/listings'),
      expect.objectContaining({ method: 'POST' })
    );
  });

  it('should submit for certification', async () => {
    vi.stubGlobal(
      'fetch',
      vi.fn().mockResolvedValue({
        ok: true,
        json: () => Promise.resolve(mockListing({ certificationStatus: 'submitted' })),
      })
    );
    const client = createMarketplaceClient(baseConfig);
    const result = await client.submitForCertification('lst_1', 'Ready for review');
    expect(result.certificationStatus).toBe('submitted');
  });

  it('should install a listing', async () => {
    vi.stubGlobal(
      'fetch',
      vi.fn().mockResolvedValue({
        ok: true,
        json: () => Promise.resolve(mockInstall()),
      })
    );
    const client = createMarketplaceClient(baseConfig);
    const result = await client.installListing('lst_1');
    expect(result.listingId).toBe('lst_1');
  });

  it('should list my installs', async () => {
    vi.stubGlobal(
      'fetch',
      vi.fn().mockResolvedValue({
        ok: true,
        json: () => Promise.resolve([mockInstall()]),
      })
    );
    const client = createMarketplaceClient(baseConfig);
    const result = await client.listMyInstalls();
    expect(result).toHaveLength(1);
  });

  it('should list reviews', async () => {
    vi.stubGlobal(
      'fetch',
      vi.fn().mockResolvedValue({
        ok: true,
        json: () => Promise.resolve([mockReview()]),
      })
    );
    const client = createMarketplaceClient(baseConfig);
    const result = await client.listReviews('lst_1');
    expect(result).toHaveLength(1);
    expect(result[0].rating).toBe(5);
  });

  it('should create a review', async () => {
    vi.stubGlobal(
      'fetch',
      vi.fn().mockResolvedValue({
        ok: true,
        json: () => Promise.resolve(mockReview()),
      })
    );
    const client = createMarketplaceClient(baseConfig);
    const result = await client.createReview('lst_1', {
      rating: 5,
      title: 'Great!',
      body: 'Love it.',
    });
    expect(result.rating).toBe(5);
  });

  it('should update a listing', async () => {
    const updated = mockListing({ title: 'Updated Title' });
    vi.stubGlobal(
      'fetch',
      vi.fn().mockResolvedValue({
        ok: true,
        json: () => Promise.resolve(updated),
      })
    );
    const client = createMarketplaceClient(baseConfig);
    const result = await client.updateListing('lst_1', { title: 'Updated Title' });
    expect(result.title).toBe('Updated Title');

    const fetchMock = globalThis.fetch as ReturnType<typeof vi.fn>;
    expect(fetchMock).toHaveBeenCalledWith(
      'http://localhost:4003/api/marketplace/listings/lst_1',
      expect.objectContaining({ method: 'PATCH' })
    );
  });

  it('should uninstall a listing', async () => {
    vi.stubGlobal(
      'fetch',
      vi.fn().mockResolvedValue({
        ok: true,
      })
    );
    const client = createMarketplaceClient(baseConfig);
    await expect(client.uninstallListing('lst_1')).resolves.toBeUndefined();

    const fetchMock = globalThis.fetch as ReturnType<typeof vi.fn>;
    expect(fetchMock).toHaveBeenCalledWith(
      'http://localhost:4003/api/marketplace/listings/lst_1/uninstall',
      expect.objectContaining({ method: 'POST' })
    );
  });

  it('should report a listing', async () => {
    vi.stubGlobal(
      'fetch',
      vi.fn().mockResolvedValue({
        ok: true,
      })
    );
    const client = createMarketplaceClient(baseConfig);
    await expect(
      client.reportListing('lst_1', { reason: 'spam', details: 'Looks like spam' })
    ).resolves.toBeUndefined();
  });

  it('should throw on non-ok response', async () => {
    vi.stubGlobal(
      'fetch',
      vi.fn().mockResolvedValue({
        ok: false,
        status: 500,
      })
    );
    const client = createMarketplaceClient(baseConfig);
    await expect(client.getListing('lst_1')).rejects.toThrow('getListing failed: 500');
  });

  it('should send correct headers', async () => {
    vi.stubGlobal(
      'fetch',
      vi.fn().mockResolvedValue({
        ok: true,
        json: () => Promise.resolve({ listings: [], total: 0 }),
      })
    );
    const client = createMarketplaceClient(baseConfig);
    await client.listListings();

    const fetchMock = globalThis.fetch as ReturnType<typeof vi.fn>;
    const callHeaders = fetchMock.mock.calls[0][1].headers as Record<string, string>;
    expect(callHeaders['x-product-id']).toBe('testapp');
    expect(callHeaders['Authorization']).toBe('Bearer test-token');
    expect(callHeaders['x-request-id']).toBeDefined();
  });
});
