import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { afterEach, beforeEach, describe, expect, it } from 'vitest';

import { importLocalDesignSystemProject } from '../src/design-system-import.js';
import { listDesignSystems, readDesignSystemAssets } from '../src/design-systems.js';

describe('importLocalDesignSystemProject', () => {
  let tempRoot: string;
  let sourceRoot: string;
  let userDesignSystemsRoot: string;

  beforeEach(() => {
    tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'od-ds-import-'));
    sourceRoot = path.join(tempRoot, 'source-app');
    userDesignSystemsRoot = path.join(tempRoot, 'user-design-systems');
    fs.mkdirSync(path.join(sourceRoot, 'src', 'components'), { recursive: true });
    fs.mkdirSync(path.join(sourceRoot, 'src', 'styles'), { recursive: true });
    fs.mkdirSync(path.join(sourceRoot, 'src', 'assets', 'fonts'), { recursive: true });
    fs.mkdirSync(path.join(sourceRoot, 'public'), { recursive: true });
    fs.writeFileSync(
      path.join(sourceRoot, 'package.json'),
      JSON.stringify({
        name: '@acme/kami-app',
        description: 'A focused workspace for AI design reviews.',
        dependencies: { react: '^18.0.0', tailwindcss: '^3.0.0' },
      }),
    );
    fs.writeFileSync(
      path.join(sourceRoot, 'README.md'),
      '# Kami App\n\nA calm review surface with crisp cards and bright primary actions.\n',
    );
    fs.writeFileSync(
      path.join(sourceRoot, 'src', 'styles', 'tokens.css'),
      ':root { --color-primary: #ff3366; --color-background: #101014; --radius-card: 12px; }',
    );
    fs.writeFileSync(
      path.join(sourceRoot, 'tailwind.config.ts'),
      'export default { theme: { extend: { colors: {}, fontFamily: {}, borderRadius: {} } } }',
    );
    fs.writeFileSync(path.join(sourceRoot, 'src', 'components', 'Button.tsx'), 'export function Button() {}');
    fs.writeFileSync(path.join(sourceRoot, 'public', 'logo.svg'), '<svg xmlns="http://www.w3.org/2000/svg" />');
    fs.writeFileSync(path.join(sourceRoot, 'src', 'assets', 'fonts', 'AcmeSans-Regular.woff2'), 'font');
  });

  afterEach(() => {
    fs.rmSync(tempRoot, { recursive: true, force: true });
  });

  it('generates a design-system project from a local app directory', async () => {
    const result = await importLocalDesignSystemProject(sourceRoot, userDesignSystemsRoot, {
      now: new Date('2026-05-18T09:00:00.000Z'),
    });

    expect(result.id).toBe('kami-app');
    expect(result.files).toEqual(
      expect.arrayContaining([
        'USAGE.md',
        'DESIGN.md',
        'tokens.css',
        'components.html',
        'components.manifest.json',
        'manifest.json',
        'assets/logo.svg',
        'fonts/acmesans-regular.woff2',
        'preview/colors.html',
        'preview/typography.html',
        'preview/spacing.html',
        'preview/components-buttons.html',
        'preview/components-inputs.html',
        'preview/app.html',
        'source/scanned-files.json',
        'source/evidence.md',
        'source/tokens.source.json',
        'source/snippets/INDEX.json',
        'source/snippets/button.tsx',
      ]),
    );

    const manifest = JSON.parse(fs.readFileSync(path.join(result.dir, 'manifest.json'), 'utf8')) as Record<string, unknown>;
    expect(manifest).toMatchObject({
      schemaVersion: 'od-design-system-project/v1',
      id: 'kami-app',
      name: 'kami app',
      category: 'Imported',
      source: {
        type: 'local',
        path: fs.realpathSync.native(sourceRoot),
        importedAt: '2026-05-18T09:00:00.000Z',
      },
      files: {
        design: 'DESIGN.md',
        tokens: 'tokens.css',
        components: 'components.html',
      },
      usage: 'USAGE.md',
      componentsManifest: 'components.manifest.json',
      importMode: 'hybrid',
      assetsDir: 'assets',
      craft: {
        applies: [],
        suggested: ['color'],
        exemptions: [],
      },
      preview: {
        dir: 'preview',
      },
      sourceFiles: {
        scanned: 'source/scanned-files.json',
        evidence: 'source/evidence.md',
        tokens: 'source/tokens.source.json',
        snippets: 'source/snippets/INDEX.json',
      },
    });
    expect((manifest.preview as { pages: unknown[] }).pages).toHaveLength(6);
    expect(manifest.fonts).toMatchObject([{ family: 'AcmeSans Regular', file: 'fonts/acmesans-regular.woff2' }]);

    const design = fs.readFileSync(path.join(result.dir, 'DESIGN.md'), 'utf8');
    expect(design).toContain('A focused workspace for AI design reviews.');
    expect(design).toContain('Button: `src/components/Button.tsx`');
    expect(design).toContain('`--color-primary: #ff3366`');

    const usage = fs.readFileSync(path.join(result.dir, 'USAGE.md'), 'utf8');
    expect(usage).toContain('Auto-generated by Open Design importer');
    expect(usage).toContain('## Read Order');
    expect(usage).toContain('source/tokens.source.json');

    const componentsManifest = JSON.parse(
      fs.readFileSync(path.join(result.dir, 'components.manifest.json'), 'utf8'),
    ) as Record<string, unknown>;
    expect(componentsManifest).toMatchObject({
      schemaVersion: 1,
      brandId: 'kami-app',
      source: {
        componentsHtml: 'components.html',
        tokensCss: 'tokens.css',
      },
    });

    const scannedFiles = JSON.parse(
      fs.readFileSync(path.join(result.dir, 'source', 'scanned-files.json'), 'utf8'),
    ) as { files: Array<{ path: string; kind: string }> };
    expect(scannedFiles.files).toEqual(
      expect.arrayContaining([
        expect.objectContaining({ path: 'src/styles/tokens.css', kind: 'style' }),
        expect.objectContaining({ path: 'src/components/Button.tsx', kind: 'component' }),
      ]),
    );

    const sourceTokens = JSON.parse(
      fs.readFileSync(path.join(result.dir, 'source', 'tokens.source.json'), 'utf8'),
    ) as { tokenCount: number; tokens: Array<{ name: string; normalizedRole?: string }> };
    expect(sourceTokens.tokenCount).toBe(3);
    expect(sourceTokens.tokens).toEqual(
      expect.arrayContaining([
        expect.objectContaining({ name: '--color-primary', normalizedRole: 'accent' }),
      ]),
    );

    const snippetsIndex = JSON.parse(
      fs.readFileSync(path.join(result.dir, 'source', 'snippets', 'INDEX.json'), 'utf8'),
    ) as { snippets: Array<{ path: string; role: string; sourcePath: string }> };
    expect(snippetsIndex.snippets).toEqual([
      expect.objectContaining({
        path: 'source/snippets/button.tsx',
        role: 'button',
        sourcePath: 'src/components/Button.tsx',
      }),
    ]);
    expect(fs.readFileSync(path.join(result.dir, 'preview', 'app.html'), 'utf8')).toContain('src/components/Button.tsx');

    const assets = await readDesignSystemAssets(userDesignSystemsRoot, 'kami-app');
    expect(assets.tokensCss).toContain('--accent: #ff3366;');
    expect(assets.tokensCss).toContain('--bg: #101014;');
    expect(assets.fixtureHtml).toContain('Component fixture');

    const systems = await listDesignSystems(userDesignSystemsRoot);
    expect(systems).toMatchObject([
      {
        id: 'kami-app',
        title: 'kami app',
        category: 'Imported',
        summary: 'A focused workspace for AI design reviews.',
      },
    ]);
  });

  it('allocates a new slug instead of colliding with existing systems', async () => {
    const first = await importLocalDesignSystemProject(sourceRoot, userDesignSystemsRoot, {
      reservedIds: ['kami-app'],
    });
    const second = await importLocalDesignSystemProject(sourceRoot, userDesignSystemsRoot, {
      reservedIds: ['kami-app'],
    });

    expect(first.id).toBe('kami-app-2');
    expect(second.id).toBe('kami-app-3');
  });

  it('writes selected importMode and applied craft semantics into manifest', async () => {
    const result = await importLocalDesignSystemProject(sourceRoot, userDesignSystemsRoot, {
      now: new Date('2026-05-18T09:00:00.000Z'),
      importMode: 'verbatim',
      craftApplies: ['color', 'accessibility-baseline', 'color'],
    });

    const manifest = JSON.parse(fs.readFileSync(path.join(result.dir, 'manifest.json'), 'utf8')) as Record<string, unknown>;
    expect(manifest).toMatchObject({
      importMode: 'verbatim',
      craft: {
        applies: ['color', 'accessibility-baseline'],
        suggested: [],
        exemptions: [],
      },
    });
  });
});
