import assert from 'node:assert/strict';
import fs from 'node:fs/promises';
import os from 'node:os';
import path from 'node:path';
import test from 'node:test';

import { PROJECT_SCHEMA_VERSION, type Project } from '../../shared/types';
import type { AssetId } from '../../shared/types/ids';

import { collectReferencedAssetIds, reconcileAssetFiles } from './assetPrune';

void test('collectReferencedAssetIds: превью, видео и аудио', () => {
  const p = {
    scenes: {
      s1: {
        previewAssetId: 'pr' as AssetId,
        previewThumbAssetId: 'th' as AssetId,
        media: {
          videos: ['v1' as AssetId],
          audios: [{ assetId: 'a1' as AssetId, autoplay: true, loop: true }],
        },
      },
    },
    campaignAudios: [{ assetId: 'ca1' as AssetId, autoplay: true, loop: true }],
    materials: [{ id: 'm1', name: 'Map', assetId: 'mat1' as AssetId }],
    npcs: [{ id: 'n1', name: 'Guard', avatarAssetId: 'npc1' as AssetId }],
  } as unknown as Project;
  const s = collectReferencedAssetIds(p);
  assert.deepEqual([...s].sort(), ['a1', 'ca1', 'mat1', 'npc1', 'pr', 'th', 'v1'].sort());
});

void test('reconcileAssetFiles: снимает осиротевшие assets и удаляет файлы', async () => {
  const tmp = await fs.mkdtemp(path.join(os.tmpdir(), 'dnd-asset-prune-'));
  const relPath = 'assets/orphan.bin';
  await fs.mkdir(path.join(tmp, 'assets'), { recursive: true });
  await fs.writeFile(path.join(tmp, relPath), Buffer.from([1, 2, 3]));

  const asset = {
    id: 'orph' as AssetId,
    type: 'audio' as const,
    mime: 'audio/wav',
    originalName: 'x.wav',
    relPath,
    sha256: 'a',
    sizeBytes: 3,
    createdAt: new Date().toISOString(),
  };

  const base = {
    id: 'p1',
    meta: {
      name: 't',
      fileBaseName: 't',
      createdAt: '',
      updatedAt: '',
      createdWithAppVersion: '1',
      appVersion: '1',
      schemaVersion: PROJECT_SCHEMA_VERSION,
    },
    currentSceneId: null,
    currentGraphNodeId: null,
    sceneGraphNodes: [],
    sceneGraphEdges: [],
  } as unknown as Project;

  const prev: Project = {
    ...base,
    scenes: {},
    campaignAudios: [],
    materials: [],
    assets: { orphan: asset } as Project['assets'],
  };
  const next: Project = {
    ...base,
    scenes: {},
    campaignAudios: [],
    materials: [],
    assets: { orphan: asset } as Project['assets'],
  };

  const out = await reconcileAssetFiles(prev, next, tmp);
  assert.ok(!('orphan' in out.assets));
  await assert.rejects(() => fs.stat(path.join(tmp, relPath)));
});

void test('reconcileAssetFiles: удаляет файл при исключении id из assets', async () => {
  const tmp = await fs.mkdtemp(path.join(os.tmpdir(), 'dnd-asset-prune-'));
  const relPath = 'assets/gone.bin';
  await fs.mkdir(path.join(tmp, 'assets'), { recursive: true });
  await fs.writeFile(path.join(tmp, relPath), Buffer.from([9]));

  const asset = {
    id: 'gone' as AssetId,
    type: 'audio' as const,
    mime: 'audio/wav',
    originalName: 'x.wav',
    relPath,
    sha256: 'b',
    sizeBytes: 1,
    createdAt: new Date().toISOString(),
  };

  const base = {
    id: 'p1',
    meta: {
      name: 't',
      fileBaseName: 't',
      createdAt: '',
      updatedAt: '',
      createdWithAppVersion: '1',
      appVersion: '1',
      schemaVersion: PROJECT_SCHEMA_VERSION,
    },
    scenes: {},
    currentSceneId: null,
    currentGraphNodeId: null,
    sceneGraphNodes: [],
    sceneGraphEdges: [],
  } as unknown as Project;

  const prev: Project = { ...base, assets: { gone: asset } as Project['assets'] };
  const next: Project = { ...base, campaignAudios: [], materials: [], assets: {} as Project['assets'] };

  const out = await reconcileAssetFiles(prev, next, tmp);
  assert.deepEqual(out.assets, {});
  await assert.rejects(() => fs.stat(path.join(tmp, relPath)));
});
