import { afterEach, beforeEach, describe, expect, it, mock, spyOn } from 'bun:test';
import type { ExecutionContext, IServiceContainer } from '@stock-bot/types';
import * as utils from '@stock-bot/utils';
import { BaseHandler } from '../src/base/BaseHandler';

// Mock fetch
const mockFetch = mock();

class TestHandler extends BaseHandler {
  async testGet(url: string, options?: any) {
    return this.http.get(url, options);
  }

  async testPost(url: string, data?: any, options?: any) {
    return this.http.post(url, data, options);
  }

  async testPut(url: string, data?: any, options?: any) {
    return this.http.put(url, data, options);
  }

  async testDelete(url: string, options?: any) {
    return this.http.delete(url, options);
  }
}

describe('BaseHandler HTTP Methods', () => {
  let handler: TestHandler;
  let mockServices: IServiceContainer;

  beforeEach(() => {
    mockServices = {
      cache: null,
      globalCache: null,
      queueManager: null,
      proxy: null,
      browser: null,
      mongodb: null,
      postgres: null,
      questdb: null,
      logger: {
        info: mock(),
        debug: mock(),
        error: mock(),
        warn: mock(),
      } as any,
    } as IServiceContainer;

    handler = new TestHandler(mockServices, 'TestHandler');

    // Mock utils.fetch
    spyOn(utils, 'fetch').mockImplementation(mockFetch);
    mockFetch.mockReset();
  });

  afterEach(() => {
    // spyOn automatically restores
  });

  describe('GET requests', () => {
    it('should make GET requests with fetch', async () => {
      const mockResponse = {
        ok: true,
        status: 200,
        statusText: 'OK',
        headers: new Headers(),
        json: async () => ({ data: 'test' }),
      };
      mockFetch.mockResolvedValue(mockResponse);

      await handler.testGet('https://api.example.com/data');

      expect(mockFetch).toHaveBeenCalledWith(
        'https://api.example.com/data',
        expect.objectContaining({
          method: 'GET',
          logger: expect.any(Object),
        })
      );
    });

    it('should pass custom options to GET requests', async () => {
      const mockResponse = {
        ok: true,
        status: 200,
        statusText: 'OK',
        headers: new Headers(),
      };
      mockFetch.mockResolvedValue(mockResponse);

      await handler.testGet('https://api.example.com/data', {
        headers: { Authorization: 'Bearer token' },
      });

      expect(mockFetch).toHaveBeenCalledWith(
        'https://api.example.com/data',
        expect.objectContaining({
          headers: { Authorization: 'Bearer token' },
          method: 'GET',
          logger: expect.any(Object),
        })
      );
    });
  });

  describe('POST requests', () => {
    it('should make POST requests with JSON data', async () => {
      const mockResponse = {
        ok: true,
        status: 200,
        statusText: 'OK',
        headers: new Headers(),
        json: async () => ({ success: true }),
      };
      mockFetch.mockResolvedValue(mockResponse);

      const data = { name: 'test', value: 123 };
      await handler.testPost('https://api.example.com/create', data);

      expect(mockFetch).toHaveBeenCalledWith(
        'https://api.example.com/create',
        expect.objectContaining({
          method: 'POST',
          body: JSON.stringify(data),
          headers: { 'Content-Type': 'application/json' },
          logger: expect.any(Object),
        })
      );
    });

    it('should merge custom headers in POST requests', async () => {
      const mockResponse = {
        ok: true,
        status: 200,
        statusText: 'OK',
        headers: new Headers(),
      };
      mockFetch.mockResolvedValue(mockResponse);

      await handler.testPost(
        'https://api.example.com/create',
        { test: 'data' },
        {
          headers: { 'X-Custom': 'value' },
        }
      );

      expect(mockFetch).toHaveBeenCalledWith(
        'https://api.example.com/create',
        expect.objectContaining({
          method: 'POST',
          body: JSON.stringify({ test: 'data' }),
          headers: {
            'Content-Type': 'application/json',
            'X-Custom': 'value',
          },
          logger: expect.any(Object),
        })
      );
    });
  });

  describe('PUT requests', () => {
    it('should make PUT requests with JSON data', async () => {
      const mockResponse = {
        ok: true,
        status: 200,
        statusText: 'OK',
        headers: new Headers(),
      };
      mockFetch.mockResolvedValue(mockResponse);

      const data = { id: 1, name: 'updated' };
      await handler.testPut('https://api.example.com/update/1', data);

      expect(mockFetch).toHaveBeenCalledWith(
        'https://api.example.com/update/1',
        expect.objectContaining({
          method: 'PUT',
          body: JSON.stringify(data),
          headers: { 'Content-Type': 'application/json' },
          logger: expect.any(Object),
        })
      );
    });

    it('should handle PUT requests with custom options', async () => {
      const mockResponse = {
        ok: true,
        status: 200,
        statusText: 'OK',
        headers: new Headers(),
      };
      mockFetch.mockResolvedValue(mockResponse);

      await handler.testPut(
        'https://api.example.com/update',
        { data: 'test' },
        {
          headers: { 'If-Match': 'etag' },
          timeout: 5000,
        }
      );

      expect(mockFetch).toHaveBeenCalledWith(
        'https://api.example.com/update',
        expect.objectContaining({
          method: 'PUT',
          body: JSON.stringify({ data: 'test' }),
          headers: {
            'Content-Type': 'application/json',
            'If-Match': 'etag',
          },
          timeout: 5000,
          logger: expect.any(Object),
        })
      );
    });
  });

  describe('DELETE requests', () => {
    it('should make DELETE requests', async () => {
      const mockResponse = {
        ok: true,
        status: 200,
        statusText: 'OK',
        headers: new Headers(),
      };
      mockFetch.mockResolvedValue(mockResponse);

      await handler.testDelete('https://api.example.com/delete/1');

      expect(mockFetch).toHaveBeenCalledWith(
        'https://api.example.com/delete/1',
        expect.objectContaining({
          method: 'DELETE',
          logger: expect.any(Object),
        })
      );
    });

    it('should pass options to DELETE requests', async () => {
      const mockResponse = {
        ok: true,
        status: 200,
        statusText: 'OK',
        headers: new Headers(),
      };
      mockFetch.mockResolvedValue(mockResponse);

      await handler.testDelete('https://api.example.com/delete/1', {
        headers: { Authorization: 'Bearer token' },
      });

      expect(mockFetch).toHaveBeenCalledWith(
        'https://api.example.com/delete/1',
        expect.objectContaining({
          headers: { Authorization: 'Bearer token' },
          method: 'DELETE',
          logger: expect.any(Object),
        })
      );
    });
  });

  describe('Error handling', () => {
    it('should propagate fetch errors', async () => {
      mockFetch.mockRejectedValue(new Error('Network error'));

      await expect(handler.testGet('https://api.example.com/data')).rejects.toThrow(
        'Network error'
      );
    });

    it('should handle non-ok responses', async () => {
      const mockResponse = {
        ok: false,
        status: 404,
        statusText: 'Not Found',
        headers: new Headers(),
      };
      mockFetch.mockResolvedValue(mockResponse);

      const response = await handler.testGet('https://api.example.com/missing');

      expect(response.ok).toBe(false);
      expect(response.status).toBe(404);
    });
  });
});
