/**
 * Unit tests for SynorCompute client
 */

import { describe, it, expect, vi, beforeEach } from 'vitest';
import { SynorCompute } from '../client';
import { Tensor } from '../tensor';

// Mock fetch for API calls
const mockFetch = vi.fn();
global.fetch = mockFetch;

describe('SynorCompute Client', () => {
  let client: SynorCompute;

  beforeEach(() => {
    mockFetch.mockReset();
    client = new SynorCompute({ apiKey: 'test-api-key' });
  });

  describe('initialization', () => {
    it('should create client with API key', () => {
      const c = new SynorCompute({ apiKey: 'my-key' });
      expect(c).toBeInstanceOf(SynorCompute);
    });

    it('should create client with config object', () => {
      const c = new SynorCompute({
        apiKey: 'my-key',
        baseUrl: 'https://custom.api.com',
        timeout: 60000,
      });
      expect(c).toBeInstanceOf(SynorCompute);
    });

    it('should throw without API key', () => {
      expect(() => new SynorCompute({ apiKey: '' })).toThrow();
    });
  });

  describe('matmul', () => {
    it('should send matmul request', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        json: () => Promise.resolve({
          jobId: 'job-123',
          status: 'completed',
          result: { data: 'base64result', shape: [2, 4], dtype: 'fp32' },
          executionTimeMs: 15,
          processor: 'gpu',
        }),
      });

      const a = Tensor.random([2, 3]);
      const b = Tensor.random([3, 4]);
      const result = await client.matmul({ a, b });

      expect(mockFetch).toHaveBeenCalledTimes(1);
      expect(result.status).toBe('completed');
      expect(result.jobId).toBe('job-123');
    });

    it('should support precision option', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        json: () => Promise.resolve({
          jobId: 'job-456',
          status: 'completed',
          result: { data: 'base64', shape: [2, 2], dtype: 'fp16' },
        }),
      });

      const a = Tensor.random([2, 2]);
      const b = Tensor.random([2, 2]);
      await client.matmul({ a, b, precision: 'fp16' });

      const [url, options] = mockFetch.mock.calls[0];
      const body = JSON.parse(options.body);
      expect(body.precision).toBe('fp16');
    });

    it('should support processor selection', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        json: () => Promise.resolve({
          jobId: 'job-789',
          status: 'completed',
          result: { data: 'base64', shape: [2, 2], dtype: 'fp32' },
        }),
      });

      const a = Tensor.random([2, 2]);
      const b = Tensor.random([2, 2]);
      await client.matmul({ a, b, processor: 'tpu' });

      const [url, options] = mockFetch.mock.calls[0];
      const body = JSON.parse(options.body);
      expect(body.processor).toBe('tpu');
    });
  });

  describe('conv2d', () => {
    it('should send conv2d request', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        json: () => Promise.resolve({
          jobId: 'conv-123',
          status: 'completed',
          result: { data: 'base64', shape: [1, 32, 30, 30], dtype: 'fp32' },
        }),
      });

      const input = Tensor.random([1, 3, 32, 32]);
      const kernel = Tensor.random([32, 3, 3, 3]);
      const result = await client.conv2d({ input, kernel });

      expect(mockFetch).toHaveBeenCalledTimes(1);
      expect(result.status).toBe('completed');
    });

    it('should support stride and padding options', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        json: () => Promise.resolve({
          jobId: 'conv-456',
          status: 'completed',
          result: { data: 'base64', shape: [1, 32, 16, 16], dtype: 'fp32' },
        }),
      });

      const input = Tensor.random([1, 3, 32, 32]);
      const kernel = Tensor.random([32, 3, 3, 3]);
      await client.conv2d({
        input,
        kernel,
        stride: [2, 2],
        padding: [1, 1],
      });

      const [url, options] = mockFetch.mock.calls[0];
      const body = JSON.parse(options.body);
      expect(body.stride).toEqual([2, 2]);
      expect(body.padding).toEqual([1, 1]);
    });
  });

  describe('attention', () => {
    it('should send attention request', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        json: () => Promise.resolve({
          jobId: 'attn-123',
          status: 'completed',
          result: { data: 'base64', shape: [1, 8, 64, 64], dtype: 'fp16' },
        }),
      });

      const query = Tensor.random([1, 8, 64, 64]);
      const key = Tensor.random([1, 8, 64, 64]);
      const value = Tensor.random([1, 8, 64, 64]);
      const result = await client.attention({ query, key, value });

      expect(mockFetch).toHaveBeenCalledTimes(1);
      expect(result.status).toBe('completed');
    });

    it('should support flash attention option', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        json: () => Promise.resolve({
          jobId: 'attn-456',
          status: 'completed',
          result: { data: 'base64', shape: [1, 8, 64, 64], dtype: 'fp16' },
        }),
      });

      const query = Tensor.random([1, 8, 64, 64]);
      const key = Tensor.random([1, 8, 64, 64]);
      const value = Tensor.random([1, 8, 64, 64]);
      await client.attention({ query, key, value, flash: true });

      const [url, options] = mockFetch.mock.calls[0];
      const body = JSON.parse(options.body);
      expect(body.flash).toBe(true);
    });
  });

  describe('inference', () => {
    it('should send inference request', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        json: () => Promise.resolve({
          jobId: 'inf-123',
          status: 'completed',
          result: 'The answer to your question is...',
          executionTimeMs: 2500,
          processor: 'gpu',
        }),
      });

      const result = await client.inference({
        model: 'llama-3-70b',
        prompt: 'What is the capital of France?',
      });

      expect(mockFetch).toHaveBeenCalledTimes(1);
      expect(result.status).toBe('completed');
      expect(result.result).toContain('answer');
    });

    it('should support inference options', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        json: () => Promise.resolve({
          jobId: 'inf-456',
          status: 'completed',
          result: 'Generated text...',
        }),
      });

      await client.inference({
        model: 'llama-3-70b',
        prompt: 'Hello',
        maxTokens: 512,
        temperature: 0.8,
        topP: 0.95,
        topK: 40,
      });

      const [url, options] = mockFetch.mock.calls[0];
      const body = JSON.parse(options.body);
      expect(body.maxTokens).toBe(512);
      expect(body.temperature).toBe(0.8);
      expect(body.topP).toBe(0.95);
      expect(body.topK).toBe(40);
    });
  });

  describe('model registry', () => {
    it('should list models', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        json: () => Promise.resolve({
          models: [
            { id: 'llama-3-70b', name: 'Llama 3 70B', category: 'llm' },
            { id: 'mistral-7b', name: 'Mistral 7B', category: 'llm' },
          ],
        }),
      });

      const models = await client.listModels();
      expect(models).toHaveLength(2);
      expect(models[0].id).toBe('llama-3-70b');
    });

    it('should filter models by category', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        json: () => Promise.resolve({
          models: [
            { id: 'sd-xl', name: 'Stable Diffusion XL', category: 'image_generation' },
          ],
        }),
      });

      await client.listModels({ category: 'image_generation' });

      const [url] = mockFetch.mock.calls[0];
      expect(url).toContain('category=image_generation');
    });

    it('should get model by ID', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        json: () => Promise.resolve({
          id: 'llama-3-70b',
          name: 'Llama 3 70B',
          category: 'llm',
          parameters: 70000000000,
          contextLength: 8192,
        }),
      });

      const model = await client.getModel('llama-3-70b');
      expect(model.id).toBe('llama-3-70b');
      expect(model.parameters).toBe(70000000000);
    });
  });

  describe('pricing and usage', () => {
    it('should get pricing', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        json: () => Promise.resolve({
          pricing: [
            { processor: 'gpu', pricePerSecond: 0.0001, availableUnits: 100 },
            { processor: 'tpu', pricePerSecond: 0.0002, availableUnits: 50 },
          ],
        }),
      });

      const pricing = await client.getPricing();
      expect(pricing).toHaveLength(2);
      expect(pricing[0].processor).toBe('gpu');
    });

    it('should get usage', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        json: () => Promise.resolve({
          totalJobs: 1000,
          completedJobs: 950,
          failedJobs: 50,
          totalComputeSeconds: 3600,
          totalCost: 0.36,
        }),
      });

      const usage = await client.getUsage();
      expect(usage.totalJobs).toBe(1000);
      expect(usage.completedJobs).toBe(950);
    });
  });

  describe('error handling', () => {
    it('should handle API errors', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: false,
        status: 401,
        json: () => Promise.resolve({ error: 'Invalid API key' }),
      });

      const a = Tensor.random([2, 2]);
      const b = Tensor.random([2, 2]);

      await expect(client.matmul({ a, b })).rejects.toThrow();
    });

    it('should handle network errors', async () => {
      mockFetch.mockRejectedValueOnce(new Error('Network error'));

      const a = Tensor.random([2, 2]);
      const b = Tensor.random([2, 2]);

      await expect(client.matmul({ a, b })).rejects.toThrow('Network error');
    });
  });

  describe('health check', () => {
    it('should return true for healthy service', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        json: () => Promise.resolve({ status: 'healthy' }),
      });

      const healthy = await client.healthCheck();
      expect(healthy).toBe(true);
    });

    it('should return false for unhealthy service', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: false,
      });

      const healthy = await client.healthCheck();
      expect(healthy).toBe(false);
    });
  });
});
