import { describe, expect, it } from 'vitest';
import {
  createRateLimitConfig,
  determineRateLimit,
  MANAGEMENT_API_DEFAULT_RATE_LIMIT,
  parseRateLimitHeaders,
} from './rateLimit';
import { StoryblokContentVersion } from './constants';
import type { ISbStoriesParams } from './interfaces';

describe('rateLimit', () => {
  describe('createRateLimitConfig', () => {
    it('should create config with user rate limit', () => {
      const config = createRateLimitConfig(50);
      expect(config.userRateLimit).toBe(50);
      expect(config.serverHeadersRateLimit).toBeUndefined();
    });

    it('should create config without user rate limit', () => {
      const config = createRateLimitConfig();
      expect(config.userRateLimit).toBeUndefined();
      expect(config.serverHeadersRateLimit).toBeUndefined();
    });
  });

  describe('parseRateLimitHeaders', () => {
    it('should parse both X-RateLimit and X-RateLimit-Policy headers', () => {
      const headers = {
        'x-ratelimit': '"concurrent-requests";r=29',
        'x-ratelimit-policy': '"concurrent-requests";q=30',
      };
      const result = parseRateLimitHeaders(headers);
      expect(result).toEqual({
        remaining: 29,
        max: 30,
      });
    });

    it('should handle case-sensitive header names', () => {
      const headers = {
        'X-RateLimit': '"concurrent-requests";r=15',
        'X-RateLimit-Policy': '"concurrent-requests";q=50',
      };
      const result = parseRateLimitHeaders(headers);
      expect(result).toEqual({
        remaining: 15,
        max: 50,
      });
    });

    it('should parse only X-RateLimit header when present', () => {
      const headers = {
        'x-ratelimit': '"concurrent-requests";r=10',
      };
      const result = parseRateLimitHeaders(headers);
      expect(result).toEqual({
        remaining: 10,
      });
    });

    it('should parse only X-RateLimit-Policy header when present', () => {
      const headers = {
        'x-ratelimit-policy': '"concurrent-requests";q=100',
      };
      const result = parseRateLimitHeaders(headers);
      expect(result).toEqual({
        max: 100,
      });
    });

    it('should return null when no rate limit headers present', () => {
      const headers = {
        'content-type': 'application/json',
      };
      const result = parseRateLimitHeaders(headers);
      expect(result).toBeNull();
    });

    it('should return null when headers is null or undefined', () => {
      expect(parseRateLimitHeaders(null)).toBeNull();
      expect(parseRateLimitHeaders(undefined)).toBeNull();
    });

    it('should handle malformed header values', () => {
      const headers = {
        'x-ratelimit': 'invalid-format',
        'x-ratelimit-policy': 'also-invalid',
      };
      const result = parseRateLimitHeaders(headers);
      expect(result).toBeNull();
    });
  });

  describe('determineRateLimit', () => {
    describe('precedence order', () => {
      it('should prefer user rate limit over server rate limit', () => {
        const params: ISbStoriesParams = {
          version: StoryblokContentVersion.DRAFT,
        };
        const config = {
          userRateLimit: 25,
          serverHeadersRateLimit: 100,
        };
        const result = determineRateLimit('/cdn/stories', params, config);
        expect(result).toBe(25);
      });

      it('should prefer user rate limit over automatic tier calculation', () => {
        const params: ISbStoriesParams = {
          version: StoryblokContentVersion.DRAFT,
          per_page: 100, // Would normally be 6 req/s
        };
        const config = {
          userRateLimit: 20,
        };
        const result = determineRateLimit('/cdn/stories', params, config);
        expect(result).toBe(20);
      });

      it('should use server rate limit when no user rate limit provided', () => {
        const params: ISbStoriesParams = {
          version: StoryblokContentVersion.DRAFT,
        };
        const config = {
          serverHeadersRateLimit: 75,
        };
        const result = determineRateLimit('/cdn/stories', params, config);
        expect(result).toBe(75);
      });

      it('should cap any rate limit at 1000', () => {
        const params: ISbStoriesParams = {
          version: StoryblokContentVersion.DRAFT,
        };

        // User rate limit capped at 1000
        const userConfig = {
          userRateLimit: 2000,
        };
        expect(determineRateLimit('/cdn/stories', params, userConfig)).toBe(1000);

        // Server headers rate limit capped at 1000
        const serverConfig = {
          serverHeadersRateLimit: 5000,
        };
        expect(determineRateLimit('/cdn/stories', params, serverConfig)).toBe(1000);
      });
    });

    describe('published requests (same rate limits as draft)', () => {
      it('should use per_page-based rate limit for published version', () => {
        const params: ISbStoriesParams = {
          version: StoryblokContentVersion.PUBLISHED,
        };
        // Default per_page is 25, so rate limit should be 50
        const result = determineRateLimit('/cdn/stories', params);
        expect(result).toBe(50);
      });

      it('should use per_page-based rate limit when version is not specified', () => {
        const params: ISbStoriesParams = {};
        // Default per_page is 25, so rate limit should be 50
        const result = determineRateLimit('/cdn/stories', params);
        expect(result).toBe(50);
      });

      it('should use 6 req/s for published with per_page 100', () => {
        const params: ISbStoriesParams = {
          version: StoryblokContentVersion.PUBLISHED,
          per_page: 100,
        };
        const result = determineRateLimit('/cdn/stories', params);
        expect(result).toBe(6);
      });

      it('should apply user rate limit to published version', () => {
        const params: ISbStoriesParams = {
          version: StoryblokContentVersion.PUBLISHED,
        };
        const config = {
          userRateLimit: 50,
        };
        const result = determineRateLimit('/cdn/stories', params, config);
        expect(result).toBe(50);
      });

      it('should apply server rate limit to published version when no user limit', () => {
        const params: ISbStoriesParams = {
          version: StoryblokContentVersion.PUBLISHED,
        };
        const config = {
          serverHeadersRateLimit: 75,
        };
        const result = determineRateLimit('/cdn/stories', params, config);
        expect(result).toBe(75);
      });

      it('should prefer user rate limit over per_page-based limit for published', () => {
        const params: ISbStoriesParams = {
          version: StoryblokContentVersion.PUBLISHED,
          per_page: 100, // Would be 6 req/s
        };
        const config = {
          userRateLimit: 10,
          serverHeadersRateLimit: 100,
        };
        const result = determineRateLimit('/cdn/stories', params, config);
        expect(result).toBe(10);
      });
    });

    describe('server rate limit without user override', () => {
      it('should use server rate limit when provided and no user rate limit', () => {
        const params: ISbStoriesParams = {
          version: StoryblokContentVersion.DRAFT,
          per_page: 100, // Would normally be 6 req/s
        };
        const config = {
          serverHeadersRateLimit: 30,
        };
        const result = determineRateLimit('/cdn/stories', params, config);
        expect(result).toBe(30);
      });
    });

    describe('uncached requests - single stories', () => {
      it('should use 50 req/s for single story by path', () => {
        const params: ISbStoriesParams = {
          version: StoryblokContentVersion.DRAFT,
        };
        const result = determineRateLimit('/cdn/stories/home', params);
        expect(result).toBe(50);
      });

      it('should use 50 req/s for single story with find_by', () => {
        const params: ISbStoriesParams = {
          version: StoryblokContentVersion.DRAFT,
        };
        // find_by is in ISbStoryParams, not ISbStoriesParams, but the logic still applies
        const result = determineRateLimit('/cdn/stories/some-uuid', params);
        expect(result).toBe(50);
      });
    });

    describe('uncached requests - listings by per_page', () => {
      it('should use 50 req/s for listings with per_page <= 25', () => {
        const params: ISbStoriesParams = {
          version: StoryblokContentVersion.DRAFT,
          per_page: 10,
        };
        const result = determineRateLimit('/cdn/stories', params);
        expect(result).toBe(50);
      });

      it('should use 50 req/s for listings with per_page = 25', () => {
        const params: ISbStoriesParams = {
          version: StoryblokContentVersion.DRAFT,
          per_page: 25,
        };
        const result = determineRateLimit('/cdn/stories', params);
        expect(result).toBe(50);
      });

      it('should use 50 req/s for listings without per_page (defaults to 25)', () => {
        const params: ISbStoriesParams = {
          version: StoryblokContentVersion.DRAFT,
        };
        const result = determineRateLimit('/cdn/stories', params);
        expect(result).toBe(50);
      });

      it('should use 15 req/s for listings with per_page 26-50', () => {
        const params: ISbStoriesParams = {
          version: StoryblokContentVersion.DRAFT,
          per_page: 26,
        };
        const result = determineRateLimit('/cdn/stories', params);
        expect(result).toBe(15);
      });

      it('should use 15 req/s for listings with per_page = 50', () => {
        const params: ISbStoriesParams = {
          version: StoryblokContentVersion.DRAFT,
          per_page: 50,
        };
        const result = determineRateLimit('/cdn/stories', params);
        expect(result).toBe(15);
      });

      it('should use 10 req/s for listings with per_page 51-75', () => {
        const params: ISbStoriesParams = {
          version: StoryblokContentVersion.DRAFT,
          per_page: 60,
        };
        const result = determineRateLimit('/cdn/stories', params);
        expect(result).toBe(10);
      });

      it('should use 10 req/s for listings with per_page = 75', () => {
        const params: ISbStoriesParams = {
          version: StoryblokContentVersion.DRAFT,
          per_page: 75,
        };
        const result = determineRateLimit('/cdn/stories', params);
        expect(result).toBe(10);
      });

      it('should use 6 req/s for listings with per_page 76-100', () => {
        const params: ISbStoriesParams = {
          version: StoryblokContentVersion.DRAFT,
          per_page: 80,
        };
        const result = determineRateLimit('/cdn/stories', params);
        expect(result).toBe(6);
      });

      it('should use 6 req/s for listings with per_page = 100', () => {
        const params: ISbStoriesParams = {
          version: StoryblokContentVersion.DRAFT,
          per_page: 100,
        };
        const result = determineRateLimit('/cdn/stories', params);
        expect(result).toBe(6);
      });
    });
  });

  describe('management API rate limiting', () => {
    it('should use default 3 req/s when no config provided', () => {
      const result = determineRateLimit(undefined, undefined, {}, MANAGEMENT_API_DEFAULT_RATE_LIMIT);
      expect(result).toBe(MANAGEMENT_API_DEFAULT_RATE_LIMIT);
    });

    it('should use default 3 req/s for Management API with empty config', () => {
      const config = {
        isManagementApi: true,
      };
      const result = determineRateLimit(undefined, undefined, config, MANAGEMENT_API_DEFAULT_RATE_LIMIT);
      expect(result).toBe(MANAGEMENT_API_DEFAULT_RATE_LIMIT);
    });

    it('should respect user rate limit over default', () => {
      const config = {
        userRateLimit: 10,
        isManagementApi: true,
      };
      const result = determineRateLimit(undefined, undefined, config, MANAGEMENT_API_DEFAULT_RATE_LIMIT);
      expect(result).toBe(10);
    });

    it('should respect server rate limit when no user override', () => {
      const config = {
        serverHeadersRateLimit: 50,
        isManagementApi: true,
      };
      const result = determineRateLimit(undefined, undefined, config, MANAGEMENT_API_DEFAULT_RATE_LIMIT);
      expect(result).toBe(50);
    });

    it('should prioritize user rate limit over server rate limit', () => {
      const config = {
        userRateLimit: 15,
        serverHeadersRateLimit: 50,
        isManagementApi: true,
      };
      const result = determineRateLimit(undefined, undefined, config, MANAGEMENT_API_DEFAULT_RATE_LIMIT);
      expect(result).toBe(15);
    });

    it('should cap user rate limit at 1000', () => {
      const config = {
        userRateLimit: 5000,
        isManagementApi: true,
      };
      const result = determineRateLimit(undefined, undefined, config, MANAGEMENT_API_DEFAULT_RATE_LIMIT);
      expect(result).toBe(1000);
    });

    it('should cap server rate limit at 1000', () => {
      const config = {
        serverHeadersRateLimit: 2000,
        isManagementApi: true,
      };
      const result = determineRateLimit(undefined, undefined, config, MANAGEMENT_API_DEFAULT_RATE_LIMIT);
      expect(result).toBe(1000);
    });

    it('should follow precedence: user > server > default (3)', () => {
      // Only default
      expect(determineRateLimit(undefined, undefined, {}, MANAGEMENT_API_DEFAULT_RATE_LIMIT)).toBe(MANAGEMENT_API_DEFAULT_RATE_LIMIT);

      // Server override
      expect(determineRateLimit(undefined, undefined, { serverHeadersRateLimit: 25 }, MANAGEMENT_API_DEFAULT_RATE_LIMIT)).toBe(25);

      // User overrides everything
      expect(determineRateLimit(undefined, undefined, {
        userRateLimit: 10,
        serverHeadersRateLimit: 25,
      }, MANAGEMENT_API_DEFAULT_RATE_LIMIT)).toBe(10);
    });

    it('should use default rate limit of 3 for MAPI requests with params (regression test)', () => {
      // This tests the bug fix where MAPI requests with params were incorrectly
      // getting rate limit 1000 instead of 3 because they were treated as "cached" requests
      const params: ISbStoriesParams = {};
      const config = {
        isManagementApi: true,
      };

      // MAPI request with empty params should use default rate limit (3)
      const result = determineRateLimit(undefined, params, config, MANAGEMENT_API_DEFAULT_RATE_LIMIT);
      expect(result).toBe(MANAGEMENT_API_DEFAULT_RATE_LIMIT);
      expect(result).toBe(3);
    });

    it('should use default rate limit of 3 for MAPI single story request', () => {
      // Simulates: getMapiClient.get(`spaces/{SPACE_ID}/stories/{STORY_ID}`)
      const url = '/spaces/123/stories/456';
      const params: ISbStoriesParams = {};
      const config = {
        isManagementApi: true,
      };

      // Should use MAPI default (3), not CDN cached rate limit (1000)
      const result = determineRateLimit(url, params, config, MANAGEMENT_API_DEFAULT_RATE_LIMIT);
      expect(result).toBe(3);
    });
  });
});
