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 {
  copyResourceManagerItem,
  createResourceManagerDirectory,
  createResourceManagerFile,
  deleteResourceManagerItem,
  getResourceManagerTree,
  listResourceManagerDirectory,
  readResourceManagerFile,
  resolveStaticContentType,
} from './resource-manager-service.js';

test('resolveStaticContentType returns html content type for resource manager html files', () => {
  assert.equal(resolveStaticContentType('/tmp/sample.html'), 'text/html; charset=utf-8');
  assert.equal(resolveStaticContentType('/tmp/sample.htm'), 'text/html; charset=utf-8');
});

test('resolveStaticContentType keeps markdown and text files unchanged', () => {
  assert.equal(resolveStaticContentType('/tmp/sample.md'), 'text/markdown; charset=utf-8');
  assert.equal(resolveStaticContentType('/tmp/sample.log'), 'text/plain; charset=utf-8');
});

test('resolveStaticContentType returns video content types for common video files', () => {
  assert.equal(resolveStaticContentType('/tmp/sample.mp4'), 'video/mp4');
  assert.equal(resolveStaticContentType('/tmp/sample.webm'), 'video/webm');
  assert.equal(resolveStaticContentType('/tmp/sample.mov'), 'video/quicktime');
});

test('resolveStaticContentType returns audio content types for common audio files', () => {
  assert.equal(resolveStaticContentType('/tmp/sample.wav'), 'audio/wav');
  assert.equal(resolveStaticContentType('/tmp/sample.mp3'), 'audio/mpeg');
  assert.equal(resolveStaticContentType('/tmp/sample.ogg'), 'audio/ogg');
  assert.equal(resolveStaticContentType('/tmp/sample.m4a'), 'audio/mp4');
});

async function withTempRepo(callback: (repoRoot: string) => Promise<void>) {
  const repoRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'resource-manager-test-'));

  try {
    await callback(repoRoot);
  } finally {
    await fs.rm(repoRoot, { recursive: true, force: true });
  }
}

test('deleteResourceManagerItem removes nested directories recursively', async () => {
  await withTempRepo(async (repoRoot) => {
    await createResourceManagerDirectory(repoRoot, '', 'docs');
    await createResourceManagerFile(repoRoot, 'docs', 'note.md', '# hello');

    await deleteResourceManagerItem(repoRoot, 'docs');

    const directory = await listResourceManagerDirectory(repoRoot, '');
    assert.deepEqual(directory.items, []);
  });
});

test('deleteResourceManagerItem keeps the root protected', async () => {
  await withTempRepo(async (repoRoot) => {
    await assert.rejects(
      () => deleteResourceManagerItem(repoRoot, ''),
      (error: unknown) =>
        error instanceof Error &&
        error.message === '루트 폴더는 삭제할 수 없습니다.' &&
        'statusCode' in error &&
        error.statusCode === 400,
    );
  });
});

test('resource manager errors expose friendly missing-item messages', async () => {
  await withTempRepo(async (repoRoot) => {
    await assert.rejects(
      () => readResourceManagerFile(repoRoot, 'missing.md'),
      (error: unknown) =>
        error instanceof Error &&
        error.message === '항목을 찾을 수 없습니다.' &&
        'statusCode' in error &&
        error.statusCode === 404,
    );
  });
});

test('copyResourceManagerItem rejects same-path copy requests with a clear message', async () => {
  await withTempRepo(async (repoRoot) => {
    await createResourceManagerFile(repoRoot, '', 'dup.md', 'a');

    await assert.rejects(
      () => copyResourceManagerItem(repoRoot, 'dup.md', '', 'dup.md'),
      (error: unknown) =>
        error instanceof Error &&
        error.message === '같은 위치로는 복사하거나 이동할 수 없습니다.' &&
        'statusCode' in error &&
        error.statusCode === 400,
    );
  });
});

test('directory modifiedAt reflects the latest nested descendant change', async () => {
  await withTempRepo(async (repoRoot) => {
    await createResourceManagerDirectory(repoRoot, '', 'docs');
    await createResourceManagerDirectory(repoRoot, 'docs', 'nested');
    await createResourceManagerFile(repoRoot, 'docs/nested', 'latest.md', 'new');

    const nestedFilePath = path.join(repoRoot, 'resource', 'docs', 'nested', 'latest.md');
    const nestedDirectoryPath = path.join(repoRoot, 'resource', 'docs', 'nested');
    const docsDirectoryPath = path.join(repoRoot, 'resource', 'docs');
    const latestModifiedAt = new Date('2026-05-13T14:15:16.000Z');
    const staleModifiedAt = new Date('2026-05-10T01:02:03.000Z');

    await fs.utimes(nestedFilePath, latestModifiedAt, latestModifiedAt);
    await fs.utimes(nestedDirectoryPath, staleModifiedAt, staleModifiedAt);
    await fs.utimes(docsDirectoryPath, staleModifiedAt, staleModifiedAt);

    const directory = await listResourceManagerDirectory(repoRoot, '');
    const docsEntry = directory.items.find((item) => item.path === 'docs');
    assert.ok(docsEntry);
    assert.equal(docsEntry.modifiedAt, latestModifiedAt.toISOString());

    const tree = await getResourceManagerTree(repoRoot);
    const docsNode = tree.tree.children?.find((item) => item.path === 'docs');
    assert.ok(docsNode);
    assert.equal(docsNode.modifiedAt, latestModifiedAt.toISOString());
  });
});

test('resource manager tree and directory listing include dot-prefixed entries', async () => {
  await withTempRepo(async (repoRoot) => {
    await createResourceManagerDirectory(repoRoot, '', '.codex_chat');
    await createResourceManagerFile(repoRoot, '', '.env', 'TOKEN=1');
    await createResourceManagerFile(repoRoot, '.codex_chat', 'note.md', '# hidden');

    const directory = await listResourceManagerDirectory(repoRoot, '');
    assert.deepEqual(
      directory.items.map((item) => item.path),
      ['.codex_chat', '.env'],
    );

    const tree = await getResourceManagerTree(repoRoot);
    assert.deepEqual(
      tree.tree.children?.map((item) => item.path),
      ['.codex_chat', '.env'],
    );
    assert.deepEqual(
      tree.tree.children?.[0]?.children?.map((item) => item.path),
      ['.codex_chat/note.md'],
    );
  });
});
