import { estimateMediaTokensForMessage } from '../client';

jest.mock('@librechat/agents', () => ({
  ...jest.requireActual('@librechat/agents'),
  extractImageDimensions: jest.fn((data: string) => {
    if (data.includes('VALID_PNG')) {
      return { width: 800, height: 600 };
    }
    return null;
  }),
  estimateAnthropicImageTokens: jest.fn(
    (w: number, h: number) => Math.ceil((w * h) / 750),
  ),
  estimateOpenAIImageTokens: jest.fn(
    (w: number, h: number) => Math.ceil((w * h) / 512) + 85,
  ),
}));

const fakeTokenCount = (text: string) => Math.ceil(text.length / 4);

describe('estimateMediaTokensForMessage', () => {
  describe('non-array content', () => {
    it('returns 0 for string content', () => {
      expect(estimateMediaTokensForMessage('hello', false)).toBe(0);
    });

    it('returns 0 for null', () => {
      expect(estimateMediaTokensForMessage(null, false)).toBe(0);
    });

    it('returns 0 for undefined', () => {
      expect(estimateMediaTokensForMessage(undefined, true)).toBe(0);
    });

    it('returns 0 for a number', () => {
      expect(estimateMediaTokensForMessage(42, false)).toBe(0);
    });
  });

  describe('empty and malformed arrays', () => {
    it('returns 0 for an empty array', () => {
      expect(estimateMediaTokensForMessage([], false)).toBe(0);
    });

    it('skips null entries', () => {
      expect(estimateMediaTokensForMessage([null, undefined], false)).toBe(0);
    });

    it('skips entries without a string type', () => {
      expect(estimateMediaTokensForMessage([{ type: 123 }, { text: 'hi' }], false)).toBe(0);
    });

    it('skips text-only blocks (not media)', () => {
      expect(estimateMediaTokensForMessage([{ type: 'text', text: 'hi' }], false)).toBe(0);
    });
  });

  describe('image_url blocks', () => {
    it('falls back to 1024 for a remote URL (non-data)', () => {
      const content = [{ type: 'image_url', image_url: 'https://example.com/img.png' }];
      expect(estimateMediaTokensForMessage(content, false)).toBe(1024);
    });

    it('falls back to 1024 when image_url is an object with non-data URL', () => {
      const content = [{ type: 'image_url', image_url: { url: 'https://example.com/img.png' } }];
      expect(estimateMediaTokensForMessage(content, true)).toBe(1024);
    });

    it('falls back to 1024 when base64 data cannot be decoded', () => {
      const content = [{ type: 'image_url', image_url: 'data:image/png;base64,SHORT' }];
      expect(estimateMediaTokensForMessage(content, false)).toBe(1024);
    });

    it('estimates tokens from decoded dimensions (OpenAI path)', () => {
      const content = [{ type: 'image_url', image_url: 'data:image/png;base64,VALID_PNG_LONG_DATA' }];
      const result = estimateMediaTokensForMessage(content, false);
      expect(result).toBeGreaterThan(0);
      expect(result).not.toBe(1024);
    });

    it('estimates tokens from decoded dimensions (Claude path)', () => {
      const content = [{ type: 'image_url', image_url: { url: 'data:image/png;base64,VALID_PNG_LONG_DATA' } }];
      const result = estimateMediaTokensForMessage(content, true);
      expect(result).toBeGreaterThan(0);
      expect(result).not.toBe(1024);
    });
  });

  describe('image blocks (Anthropic format)', () => {
    it('falls back to 1024 when source is not base64', () => {
      const content = [{ type: 'image', source: { type: 'url', data: 'https://example.com' } }];
      expect(estimateMediaTokensForMessage(content, true)).toBe(1024);
    });

    it('falls back to 1024 when dimensions cannot be extracted', () => {
      const content = [{ type: 'image', source: { type: 'base64', data: 'INVALID' } }];
      expect(estimateMediaTokensForMessage(content, true)).toBe(1024);
    });

    it('estimates tokens from valid base64 image data', () => {
      const content = [{ type: 'image', source: { type: 'base64', data: 'VALID_PNG' } }];
      const result = estimateMediaTokensForMessage(content, true);
      expect(result).toBeGreaterThan(0);
      expect(result).not.toBe(1024);
    });
  });

  describe('image_file blocks', () => {
    it('falls back to 1024 (no base64 extraction path)', () => {
      const content = [{ type: 'image_file', file_id: 'file-abc' }];
      expect(estimateMediaTokensForMessage(content, false)).toBe(1024);
    });
  });

  describe('document blocks - LangChain format (source_type)', () => {
    it('counts tokens for text source_type with getTokenCount', () => {
      const content = [{
        type: 'document',
        source_type: 'text',
        text: 'a'.repeat(400),
      }];
      expect(estimateMediaTokensForMessage(content, false, fakeTokenCount)).toBe(100);
    });

    it('falls back to length/4 without getTokenCount', () => {
      const content = [{
        type: 'document',
        source_type: 'text',
        text: 'a'.repeat(400),
      }];
      expect(estimateMediaTokensForMessage(content, false)).toBe(100);
    });

    it('estimates PDF pages for base64 source_type with application/pdf mime', () => {
      const pdfData = 'x'.repeat(150_000);
      const content = [{
        type: 'document',
        source_type: 'base64',
        data: pdfData,
        mime_type: 'application/pdf',
      }];
      const result = estimateMediaTokensForMessage(content, false);
      expect(result).toBe(2 * 1500);
    });

    it('uses Claude PDF rate when isClaude is true', () => {
      const pdfData = 'x'.repeat(150_000);
      const content = [{
        type: 'document',
        source_type: 'base64',
        data: pdfData,
        mime_type: 'application/pdf',
      }];
      const result = estimateMediaTokensForMessage(content, true);
      expect(result).toBe(2 * 2000);
    });

    it('defaults to PDF estimation for empty mime_type', () => {
      const pdfData = 'x'.repeat(10);
      const content = [{
        type: 'document',
        source_type: 'base64',
        data: pdfData,
        mime_type: '',
      }];
      const result = estimateMediaTokensForMessage(content, false);
      expect(result).toBe(1 * 1500);
    });

    it('handles image/* mime inside base64 source_type', () => {
      const content = [{
        type: 'document',
        source_type: 'base64',
        data: 'VALID_PNG',
        mime_type: 'image/png',
      }];
      const result = estimateMediaTokensForMessage(content, true);
      expect(result).toBeGreaterThan(0);
      expect(result).not.toBe(1024);
    });

    it('falls back to 1024 for undecodable image in base64 source_type', () => {
      const content = [{
        type: 'document',
        source_type: 'base64',
        data: 'BAD_DATA',
        mime_type: 'image/jpeg',
      }];
      expect(estimateMediaTokensForMessage(content, false)).toBe(1024);
    });

    it('falls back to URL_DOCUMENT_FALLBACK_TOKENS for unrecognized source_type', () => {
      const content = [{ type: 'document', source_type: 'url' }];
      expect(estimateMediaTokensForMessage(content, false)).toBe(2000);
    });
  });

  describe('document blocks - Anthropic format (source object)', () => {
    it('counts tokens for text source type with getTokenCount', () => {
      const content = [{
        type: 'document',
        source: { type: 'text', data: 'a'.repeat(800) },
      }];
      expect(estimateMediaTokensForMessage(content, true, fakeTokenCount)).toBe(200);
    });

    it('falls back to length/4 for text source without getTokenCount', () => {
      const content = [{
        type: 'document',
        source: { type: 'text', data: 'a'.repeat(800) },
      }];
      expect(estimateMediaTokensForMessage(content, true)).toBe(200);
    });

    it('estimates PDF pages for base64 source with application/pdf', () => {
      const pdfData = 'x'.repeat(225_000);
      const content = [{
        type: 'document',
        source: { type: 'base64', data: pdfData, media_type: 'application/pdf' },
      }];
      const result = estimateMediaTokensForMessage(content, true);
      expect(result).toBe(3 * 2000);
    });

    it('returns URL fallback for url source type', () => {
      const content = [{
        type: 'document',
        source: { type: 'url' },
      }];
      expect(estimateMediaTokensForMessage(content, false)).toBe(2000);
    });

    it('handles content source type with nested images', () => {
      const content = [{
        type: 'document',
        source: {
          type: 'content',
          content: [
            { type: 'image', source: { type: 'base64', data: 'VALID_PNG' } },
            { type: 'image', source: { type: 'base64', data: 'UNDECODABLE' } },
          ],
        },
      }];
      const result = estimateMediaTokensForMessage(content, true);
      expect(result).toBeGreaterThan(1024);
    });

    it('falls back to URL_DOCUMENT_FALLBACK_TOKENS when source has unknown type', () => {
      const content = [{ type: 'document', source: { type: 'unknown_format' } }];
      expect(estimateMediaTokensForMessage(content, false)).toBe(2000);
    });
  });

  describe('file blocks', () => {
    it('uses same logic as document for file type blocks', () => {
      const content = [{
        type: 'file',
        source_type: 'text',
        text: 'a'.repeat(120),
      }];
      expect(estimateMediaTokensForMessage(content, false, fakeTokenCount)).toBe(30);
    });

    it('falls back to URL_DOCUMENT_FALLBACK_TOKENS for file without source info', () => {
      const content = [{ type: 'file' }];
      expect(estimateMediaTokensForMessage(content, false)).toBe(2000);
    });
  });

  describe('mixed content arrays', () => {
    it('sums tokens across multiple media blocks', () => {
      const content = [
        { type: 'text', text: 'hello' },
        { type: 'image_url', image_url: 'https://example.com/img.png' },
        { type: 'image_file', file_id: 'f1' },
        { type: 'document', source: { type: 'url' } },
      ];
      const result = estimateMediaTokensForMessage(content, false);
      expect(result).toBe(1024 + 1024 + 2000);
    });
  });
});
