import { describe, it, expect, beforeAll } from 'vitest';
import { readFileSync, existsSync } from 'fs';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
import { parseColightData, loadColightFile } from '../../src/colight/js/format.js';

const __dirname = dirname(fileURLToPath(import.meta.url));
const testArtifactsDir = join(__dirname, '..', 'test-artifacts');
const testColightFile = join(testArtifactsDir, 'test-raster.colight');

describe('Colight Format', () => {
  beforeAll(() => {
    // This test relies on Python tests creating the test file first
    if (!existsSync(testColightFile)) {
      throw new Error(
        `Test artifact ${testColightFile} not found. Make sure Python tests run first to create test files.`
      );
    }
  });

  it('should parse a .colight file created by Python', async () => {
    const fileData = readFileSync(testColightFile);
    const data = parseColightData(fileData);
    const { buffers } = data;

    // Verify structure
    expect(data).toBeDefined();
    expect(data.ast).toBeDefined();
    expect(data.bufferLayout).toBeDefined();
    expect(buffers).toBeDefined();
    expect(Array.isArray(buffers)).toBe(true);

    // Verify buffer layout
    const layout = data.bufferLayout;
    expect(layout.offsets).toBeDefined();
    expect(layout.lengths).toBeDefined();
    expect(layout.count).toBeDefined();
    expect(layout.totalSize).toBeDefined();

    // Verify buffers match layout
    expect(buffers.length).toBe(layout.count);

    // Verify each buffer has correct length
    for (let i = 0; i < buffers.length; i++) {
      // Accept both Uint8Array and Node.js Buffer
      expect(buffers[i]).toSatisfy(buf => buf instanceof Uint8Array || (typeof Buffer !== 'undefined' && buf instanceof Buffer));
      expect(buffers[i].length).toBe(layout.lengths[i]);
    }

    // Verify total size matches
    const totalSize = buffers.reduce((sum, buf) => sum + buf.length, 0);
    expect(totalSize).toBe(layout.totalSize);
  });

  it('should validate file format correctly', () => {
    // Test with invalid magic bytes
    const invalidMagic = new Uint8Array(96);
    invalidMagic.set(new TextEncoder().encode('INVALID\0'), 0);

    expect(() => parseColightData(invalidMagic)).toThrow('Wrong magic bytes');

    // Test with too short data
    const tooShort = new Uint8Array(50);
    expect(() => parseColightData(tooShort)).toThrow('Too short');
  });

  it('should handle buffer references in AST', () => {
    const fileData = readFileSync(testColightFile);
    const data = parseColightData(fileData);
    const {buffers} = data;

    // Function to recursively find buffer references
    function findBufferRefs(obj, refs = []) {
      if (obj && typeof obj === 'object') {
        if (obj.__type__ === 'buffer' && typeof obj.index === 'number') {
          refs.push(obj.index);
        } else if (obj.__buffer_index__ !== undefined) {
          refs.push(obj.__buffer_index__);
        }

        if (Array.isArray(obj)) {
          obj.forEach(item => findBufferRefs(item, refs));
        } else {
          Object.values(obj).forEach(value => findBufferRefs(value, refs));
        }
      }
      return refs;
    }

    const bufferRefs = findBufferRefs(data);

    // All buffer references should be valid indices
    for (const ref of bufferRefs) {
      expect(ref).toBeGreaterThanOrEqual(0);
      expect(ref).toBeLessThan(buffers.length);
      expect(buffers[ref]).toSatisfy(buf => buf instanceof Uint8Array || (typeof Buffer !== 'undefined' && buf instanceof Buffer));
    }
  });

  it('should create buffer views efficiently', () => {
    const fileData = readFileSync(testColightFile);
    const data = parseColightData(fileData);
    const {buffers} = data;

    // Verify buffers are typed arrays (not copies)
    for (const buffer of buffers) {
      expect(buffer).toSatisfy(buf => buf instanceof Uint8Array || (typeof Buffer !== 'undefined' && buf instanceof Buffer));
      expect(buffer.length).toBeGreaterThan(0);
    }
  });

  it('should load file from URL (when used with a server)', async () => {
    // This test would require a test server, so we'll just test the file loading logic
    // by creating a mock fetch
    const originalFetch = global.fetch;

    try {
      const fileData = readFileSync(testColightFile);
      // Convert Node.js Buffer to ArrayBuffer properly
      const arrayBuffer = new ArrayBuffer(fileData.length);
      const view = new Uint8Array(arrayBuffer);
      view.set(fileData);

      global.fetch = async () => ({
        ok: true,
        arrayBuffer: async () => arrayBuffer
      });

      const data = await loadColightFile('mock://test.colight');
      const {buffers} = data;

      expect(data).toBeDefined();
      expect(buffers).toBeDefined();
      expect(Array.isArray(buffers)).toBe(true);
    } finally {
      global.fetch = originalFetch;
    }
  });

  it('should handle fetch errors gracefully', async () => {
    const originalFetch = global.fetch;

    try {
      global.fetch = async () => ({
        ok: false,
        status: 404,
        statusText: 'Not Found'
      });

      await expect(loadColightFile('mock://missing.colight')).rejects.toThrow('Failed to fetch');
    } finally {
      global.fetch = originalFetch;
    }
  });
});
