import { describe, beforeEach, afterEach, it, expect, vi, beforeAll, afterAll } from 'vitest';
import { SimpleFetchTransport } from '../../../src/transports/fetch';
import { http, HttpResponse, HttpHandler } from 'msw';
import { setupServer } from 'msw/node';
import { createLogEvent } from '../../lib/mock';
import { LogLevel } from 'src/logger';

describe('SimpleFetchTransport', () => {
  let transport: SimpleFetchTransport;
  const API_URL = 'https://api.example.com/logs';

  const handlers: HttpHandler[] = [
    http.post(API_URL, async ({ request }) => {
      const body = await request.json();
      return HttpResponse.json({ success: true, receivedLogs: body });
    }),
  ];

  const server = setupServer(...handlers);

  beforeEach(() => {
    vi.useFakeTimers();
  });

  beforeAll(() => {
    server.listen();
  });

  afterAll(() => {
    server.close();
  });

  afterEach(() => {
    server.resetHandlers();
    vi.clearAllTimers();
    vi.useRealTimers();
  });

  describe('basic functionality', () => {
    beforeEach(() => {
      transport = new SimpleFetchTransport({
        input: API_URL,
        autoFlush: false,
      });
    });

    it('should not flush automatically when autoFlush is false', async () => {
      const requestSpy = vi.fn();
      server.use(
        http.post(API_URL, async () => {
          requestSpy();
          return HttpResponse.json({ success: true });
        }),
      );

      transport.log([createLogEvent()]);
      await vi.runAllTimersAsync();

      expect(requestSpy).not.toHaveBeenCalled();
    });

    it('should flush logs when manually called', async () => {
      let receivedBody: any;
      server.use(
        http.post(API_URL, async ({ request }) => {
          receivedBody = await request.json();
          return HttpResponse.json({ success: true });
        }),
      );

      const logEvent = createLogEvent();
      transport.log([logEvent]);
      await transport.flush();

      expect(receivedBody).toEqual([logEvent]);
    });

    it('should not make request when there are no events to flush', async () => {
      const requestSpy = vi.fn();
      server.use(
        http.post(API_URL, async () => {
          requestSpy();
          return HttpResponse.json({ success: true });
        }),
      );

      await transport.flush();
      expect(requestSpy).not.toHaveBeenCalled();
    });

    it('should handle request errors gracefully', async () => {
      const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
      server.use(
        http.post(API_URL, () => {
          return HttpResponse.error();
        }),
      );

      transport.log([createLogEvent()]);
      await transport.flush();

      expect(consoleErrorSpy).toHaveBeenCalled();
      consoleErrorSpy.mockRestore();
    });
  });

  describe('auto-flush behavior', () => {
    it('should auto-flush after default delay when autoFlush is true', async () => {
      const requestSpy = vi.fn();
      server.use(
        http.post(API_URL, async () => {
          requestSpy();
          return HttpResponse.json({ success: true });
        }),
      );

      transport = new SimpleFetchTransport({
        input: API_URL,
        autoFlush: true,
      });

      transport.log([createLogEvent()]);

      // Default delay is 2000ms
      await vi.advanceTimersByTimeAsync(2000);

      expect(requestSpy).toHaveBeenCalledTimes(1);
    });

    it('should auto-flush after custom delay', async () => {
      const requestSpy = vi.fn();
      server.use(
        http.post(API_URL, async () => {
          requestSpy();
          return HttpResponse.json({ success: true });
        }),
      );

      transport = new SimpleFetchTransport({
        input: API_URL,
        autoFlush: { durationMs: 1000 },
      });

      transport.log([createLogEvent()]);

      await vi.advanceTimersByTimeAsync(999);
      expect(requestSpy).not.toHaveBeenCalled();

      await vi.advanceTimersByTimeAsync(1);
      expect(requestSpy).toHaveBeenCalledTimes(1);
    });

    it('should reset auto-flush timer when new logs are added', async () => {
      let receivedBody: any;
      server.use(
        http.post(API_URL, async ({ request }) => {
          receivedBody = await request.json();
          return HttpResponse.json({ success: true });
        }),
      );

      transport = new SimpleFetchTransport({
        input: API_URL,
        autoFlush: { durationMs: 1000 },
      });

      transport.log([createLogEvent(LogLevel.info, 'first')]);

      await vi.advanceTimersByTimeAsync(500);
      transport.log([createLogEvent(LogLevel.info, 'second')]);

      await vi.advanceTimersByTimeAsync(500);
      expect(receivedBody).toBeUndefined();

      await vi.advanceTimersByTimeAsync(500);
      expect(receivedBody).toBeDefined();
      expect(receivedBody[0].message).toBe('first');
      expect(receivedBody[1].message).toBe('second');
    });

    it('should auto-flush with custom duration from config object', async () => {
      const requestSpy = vi.fn();
      server.use(
        http.post(API_URL, async () => {
          requestSpy();
          return HttpResponse.json({ success: true });
        }),
      );

      transport = new SimpleFetchTransport({
        input: API_URL,
        autoFlush: { durationMs: 500 },
      });

      transport.log([createLogEvent()]);

      await vi.advanceTimersByTimeAsync(499);
      expect(requestSpy).not.toHaveBeenCalled();

      await vi.advanceTimersByTimeAsync(1);
      expect(requestSpy).toHaveBeenCalledTimes(1);
    });
  });

  describe('custom fetch configuration', () => {
    it('should respect custom fetch init options', async () => {
      let receivedHeaders: Headers = new Headers();
      server.use(
        http.post(API_URL, async ({ request }) => {
          receivedHeaders = request.headers;
          return HttpResponse.json({ success: true });
        }),
      );

      transport = new SimpleFetchTransport({
        input: API_URL,
        init: {
          headers: {
            'Content-Type': 'application/json',
            'X-Custom-Header': 'test',
          },
          credentials: 'include',
        },
      });

      transport.log([createLogEvent()]);
      await transport.flush();

      expect(receivedHeaders.get('X-Custom-Header')).toBe('test');
      expect(receivedHeaders.get('Content-Type')).toBe('application/json');
    });
  });

  describe('log level filtering', () => {
    it('should filter logs based on logLevel', async () => {
      let receivedLogs: any[] = [];
      server.use(
        http.post(API_URL, async ({ request }) => {
          receivedLogs = (await request.json()) as any[];
          return HttpResponse.json({ success: true });
        }),
      );

      transport = new SimpleFetchTransport({
        input: API_URL,
        logLevel: LogLevel.warn,
      });

      transport.log([
        createLogEvent(LogLevel.debug, 'debug message'),
        createLogEvent(LogLevel.info, 'info message'),
        createLogEvent(LogLevel.warn, 'warn message'),
        createLogEvent(LogLevel.error, 'error message'),
      ]);

      await transport.flush();

      expect(receivedLogs).toHaveLength(2);
      expect(receivedLogs.map((log) => log.level)).toEqual(['warn', 'error']);
    });

    it('should use info as default logLevel', async () => {
      let receivedLogs: any[] = [];
      server.use(
        http.post(API_URL, async ({ request }) => {
          receivedLogs = (await request.json()) as any[];
          return HttpResponse.json({ success: true });
        }),
      );

      transport = new SimpleFetchTransport({ input: API_URL });

      transport.log([
        createLogEvent(LogLevel.debug, 'debug message'),
        createLogEvent(LogLevel.info, 'info message'),
        createLogEvent(LogLevel.warn, 'warn message'),
      ]);

      await transport.flush();

      expect(receivedLogs).toHaveLength(2);
      expect(receivedLogs.map((log) => log.level)).toEqual(['info', 'warn']);
    });
  });
});
