import path from "node:path";
import { readFile } from "node:fs/promises";

import { describe, expect, it } from "vitest";

import { importModelAssetFromFile, importModelAssetFromFiles, loadModelAssetFromStorage } from "../../src/assets/gltf-model-import";
import { createInMemoryProjectAssetStorage } from "../../src/assets/project-asset-storage";

const tinyGlbFixturePath = path.resolve(process.cwd(), "fixtures/assets/tiny-triangle.glb");
const externalTriangleGltfPath = path.resolve(process.cwd(), "fixtures/assets/external-triangle/scene.gltf");
const externalTriangleBinPath = path.resolve(process.cwd(), "fixtures/assets/external-triangle/triangle.bin");

function createTestFile(bytes: Uint8Array | Buffer, name: string, type: string): File {
  const arrayBuffer = new ArrayBuffer(bytes.byteLength);
  new Uint8Array(arrayBuffer).set(bytes);

  return {
    name,
    type,
    lastModified: Date.now(),
    size: arrayBuffer.byteLength,
    webkitRelativePath: "",
    arrayBuffer: async () => arrayBuffer.slice(0)
  } as File;
}

describe("model import", () => {
  it("imports and reloads a tiny GLB fixture", async () => {
    const storage = createInMemoryProjectAssetStorage();
    const fileBytes = await readFile(tinyGlbFixturePath);
    const file = createTestFile(fileBytes, "tiny-triangle.glb", "model/gltf-binary");

    const importedModel = await importModelAssetFromFile(file, storage);

    expect(importedModel.asset.mimeType).toBe("model/gltf-binary");
    expect(importedModel.asset.metadata.format).toBe("glb");
    expect(importedModel.asset.byteLength).toBe(fileBytes.byteLength);
    expect(importedModel.modelInstance.assetId).toBe(importedModel.asset.id);

    const storedAsset = await storage.getAsset(importedModel.asset.storageKey);

    expect(Object.keys(storedAsset?.files ?? {})).toEqual(["tiny-triangle.glb"]);

    const reloadedAsset = await loadModelAssetFromStorage(storage, importedModel.asset);

    expect(reloadedAsset.metadata.format).toBe("glb");
    expect(reloadedAsset.template.children.length).toBeGreaterThan(0);
  });

  it("imports and reloads a gltf fixture with external resources", async () => {
    const storage = createInMemoryProjectAssetStorage();
    const gltfBytes = await readFile(externalTriangleGltfPath);
    const binBytes = await readFile(externalTriangleBinPath);

    const importedModel = await importModelAssetFromFiles(
      [
        createTestFile(binBytes, "triangle.bin", "application/octet-stream"),
        createTestFile(gltfBytes, "scene.gltf", "model/gltf+json")
      ],
      storage
    );

    expect(importedModel.asset.mimeType).toBe("model/gltf+json");
    expect(importedModel.asset.metadata.format).toBe("gltf");
    expect(importedModel.asset.byteLength).toBe(gltfBytes.byteLength + binBytes.byteLength);

    const storedAsset = await storage.getAsset(importedModel.asset.storageKey);

    expect(Object.keys(storedAsset?.files ?? {}).sort()).toEqual(["scene.gltf", "triangle.bin"]);

    const reloadedAsset = await loadModelAssetFromStorage(storage, importedModel.asset);

    expect(reloadedAsset.metadata.meshCount).toBe(1);
    expect(reloadedAsset.template.children.length).toBeGreaterThan(0);
  });
});
