import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
import { join } from 'node:path';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import {
  cmdExport,
  generateCSSVars,
  generateFigmaTokens,
  generateShadcnComponentsJSON,
  generateShadcnCSS,
  generateTailwindConfig,
  parseTokensCSS,
  SHADCN_MAP,
  TAILWIND_COLOR_MAP,
} from '../src/commands/export.js';

const SAMPLE_TOKENS = `/* Generated by @decantr/cli */
:root {
  --d-primary: #7C93B0;
  --d-secondary: #A1A1AA;
  --d-accent: #6B8AAE;
  --d-bg: #18181B;
  --d-surface: #1F1F23;
  --d-surface-raised: #27272A;
  --d-border: #3F3F46;
  --d-text: #FAFAFA;
  --d-text-muted: #A1A1AA;
  --d-primary-hover: #8CA3C0;
  --d-gap-4: 1rem;
  --d-radius: 0.5rem;
  --d-radius-sm: 0.25rem;
  --d-radius-lg: 0.75rem;
  --d-radius-xl: 1rem;
  --d-radius-full: 9999px;
  --d-shadow-sm: 0 1px 2px rgba(0,0,0,0.2);
  --d-shadow: 0 1px 3px rgba(0,0,0,0.25);
  --d-shadow-md: 0 4px 6px rgba(0,0,0,0.3);
  --d-shadow-lg: 0 10px 15px rgba(0,0,0,0.4);
  --d-success: #22C55E;
  --d-error: #EF4444;
  --d-warning: #F59E0B;
  --d-info: #3b82f6;
}
`;

const MINIMAL_ESSENCE = JSON.stringify({
  version: '4.0.0',
  dna: {
    theme: { id: 'carbon', mode: 'dark', shape: 'rounded' },
    spacing: { base_unit: 4, scale: 'linear', density: 'comfortable', content_gap: '1.5rem' },
    typography: { scale: 'major-third', heading_weight: 600, body_weight: 400 },
    color: { palette: 'neutral', accent_count: 1, cvd_preference: 'none' },
    radius: { philosophy: 'rounded', base: 8 },
    elevation: { system: 'shadow', max_levels: 3 },
    motion: { preference: 'reduced', duration_scale: 1, reduce_motion: true },
    accessibility: { wcag_level: 'AA', focus_visible: true, skip_nav: true },
    personality: ['professional'],
  },
  blueprint: { sections: [], features: [], routes: {} },
  meta: {
    archetype: 'test',
    target: 'react-spa',
    platform: { type: 'spa', routing: 'hash' },
    guard: { mode: 'guided', dna_enforcement: 'error', blueprint_enforcement: 'warn' },
  },
});

describe('parseTokensCSS', () => {
  it('parses all --d-* tokens from CSS', () => {
    const tokens = parseTokensCSS(SAMPLE_TOKENS);

    expect(tokens.get('--d-primary')).toBe('#7C93B0');
    expect(tokens.get('--d-bg')).toBe('#18181B');
    expect(tokens.get('--d-radius')).toBe('0.5rem');
    expect(tokens.get('--d-shadow-sm')).toBe('0 1px 2px rgba(0,0,0,0.2)');
    expect(tokens.size).toBeGreaterThan(10);
  });

  it('returns empty map for empty CSS', () => {
    const tokens = parseTokensCSS('');
    expect(tokens.size).toBe(0);
  });

  it('ignores non --d-* variables', () => {
    const css = ':root { --custom: red; --d-bg: #000; }';
    const tokens = parseTokensCSS(css);
    expect(tokens.size).toBe(1);
    expect(tokens.get('--d-bg')).toBe('#000');
  });
});

describe('generateShadcnCSS', () => {
  it('maps decantr tokens to shadcn variables', () => {
    const tokens = parseTokensCSS(SAMPLE_TOKENS);
    const css = generateShadcnCSS(tokens);

    expect(css).toContain('--background: #18181B;');
    expect(css).toContain('--foreground: #FAFAFA;');
    expect(css).toContain('--card: #1F1F23;');
    expect(css).toContain('--popover: #27272A;');
    expect(css).toContain('--primary: #7C93B0;');
    expect(css).toContain('--border: #3F3F46;');
    expect(css).toContain('--muted-foreground: #A1A1AA;');
    expect(css).toContain('--destructive: #EF4444;');
    expect(css).toContain('--radius: 0.5rem;');
  });

  it('includes derived foreground tokens', () => {
    const tokens = parseTokensCSS(SAMPLE_TOKENS);
    const css = generateShadcnCSS(tokens);

    expect(css).toContain('--card-foreground: #FAFAFA;');
    expect(css).toContain('--popover-foreground: #FAFAFA;');
    expect(css).toContain('--muted: #1F1F23;');
  });

  it('wraps in @layer base', () => {
    const tokens = parseTokensCSS(SAMPLE_TOKENS);
    const css = generateShadcnCSS(tokens);

    expect(css).toContain('@layer base {');
    expect(css).toContain(':root {');
  });
});

describe('generateShadcnComponentsJSON', () => {
  it('produces valid JSON with shadcn schema', () => {
    const json = generateShadcnComponentsJSON();
    const parsed = JSON.parse(json);

    expect(parsed.$schema).toBe('https://ui.shadcn.com/schema.json');
    expect(parsed.style).toBe('default');
    expect(parsed.tailwind.css).toBe('src/styles/shadcn-theme.css');
  });
});

describe('generateTailwindConfig', () => {
  it('maps color tokens to Tailwind extend.colors', () => {
    const tokens = parseTokensCSS(SAMPLE_TOKENS);
    const config = generateTailwindConfig(tokens);

    expect(config).toContain("'primary': '#7C93B0'");
    expect(config).toContain("'background': '#18181B'");
    expect(config).toContain("'foreground': '#FAFAFA'");
    expect(config).toContain("'error': '#EF4444'");
  });

  it('maps radius tokens to Tailwind extend.borderRadius', () => {
    const tokens = parseTokensCSS(SAMPLE_TOKENS);
    const config = generateTailwindConfig(tokens);

    expect(config).toContain("'DEFAULT': '0.5rem'");
    expect(config).toContain("'sm': '0.25rem'");
    expect(config).toContain("'lg': '0.75rem'");
  });

  it('maps shadow tokens to Tailwind extend.boxShadow', () => {
    const tokens = parseTokensCSS(SAMPLE_TOKENS);
    const config = generateTailwindConfig(tokens);

    expect(config).toContain("'sm': '0 1px 2px rgba(0,0,0,0.2)'");
    expect(config).toContain("'DEFAULT': '0 1px 3px rgba(0,0,0,0.25)'");
  });

  it('exports valid TypeScript', () => {
    const tokens = parseTokensCSS(SAMPLE_TOKENS);
    const config = generateTailwindConfig(tokens);

    expect(config).toContain("import type { Config } from 'tailwindcss';");
    expect(config).toContain('satisfies Partial<Config>');
  });
});

describe('generateCSSVars', () => {
  it('outputs all tokens as CSS custom properties', () => {
    const tokens = parseTokensCSS(SAMPLE_TOKENS);
    const css = generateCSSVars(tokens);

    expect(css).toContain('--d-primary: #7C93B0;');
    expect(css).toContain('--d-radius: 0.5rem;');
    expect(css).toContain('--d-shadow-lg: 0 10px 15px rgba(0,0,0,0.4);');
    expect(css).toContain(':root {');
  });
});

describe('generateFigmaTokens', () => {
  it('exports Tokens Studio-compatible token JSON', () => {
    const tokens = parseTokensCSS(SAMPLE_TOKENS);
    const parsed = JSON.parse(generateFigmaTokens(tokens)) as Record<
      string,
      { $type: string; $value: string }
    >;

    expect(parsed['d.primary'].$type).toBe('color');
    expect(parsed['d.primary'].$value).toBe('#7C93B0');
    expect(parsed['d.radius'].$type).toBe('borderRadius');
  });
});

describe('cmdExport', () => {
  const tmpDir = join(import.meta.dirname ?? '.', '__export-test-tmp__');

  beforeEach(() => {
    rmSync(tmpDir, { recursive: true, force: true });
    mkdirSync(join(tmpDir, 'src', 'styles'), { recursive: true });
    writeFileSync(join(tmpDir, 'decantr.essence.json'), MINIMAL_ESSENCE, 'utf-8');
    writeFileSync(join(tmpDir, 'src', 'styles', 'tokens.css'), SAMPLE_TOKENS, 'utf-8');
  });

  afterEach(() => {
    rmSync(tmpDir, { recursive: true, force: true });
  });

  it('exports shadcn theme CSS and components.json', async () => {
    await cmdExport('shadcn', tmpDir);

    const cssPath = join(tmpDir, 'src', 'styles', 'shadcn-theme.css');
    const jsonPath = join(tmpDir, 'components.json');

    expect(existsSync(cssPath)).toBe(true);
    expect(existsSync(jsonPath)).toBe(true);

    const css = readFileSync(cssPath, 'utf-8');
    expect(css).toContain('--background:');
    expect(css).toContain('--primary:');

    const json = JSON.parse(readFileSync(jsonPath, 'utf-8'));
    expect(json.$schema).toBe('https://ui.shadcn.com/schema.json');
  });

  it('exports tailwind config', async () => {
    await cmdExport('tailwind', tmpDir);

    const outPath = join(tmpDir, 'tailwind.decantr.config.ts');
    expect(existsSync(outPath)).toBe(true);

    const content = readFileSync(outPath, 'utf-8');
    expect(content).toContain('colors:');
    expect(content).toContain('borderRadius:');
  });

  it('exports css-vars file', async () => {
    await cmdExport('css-vars', tmpDir);

    const outPath = join(tmpDir, 'decantr-tokens.css');
    expect(existsSync(outPath)).toBe(true);

    const content = readFileSync(outPath, 'utf-8');
    expect(content).toContain('--d-primary:');
  });

  it('exports Figma/Tokens Studio JSON', async () => {
    await cmdExport('figma-tokens', tmpDir);

    const outPath = join(tmpDir, '.decantr', 'design', 'figma-tokens.json');
    expect(existsSync(outPath)).toBe(true);

    const content = JSON.parse(readFileSync(outPath, 'utf-8')) as Record<
      string,
      { $type: string; $value: string }
    >;
    expect(content['d.primary'].$type).toBe('color');
    expect(content['d.radius'].$value).toBe('0.5rem');
  });

  it('respects --output option', async () => {
    const customOut = join(tmpDir, 'custom', 'out.css');
    await cmdExport('css-vars', tmpDir, { output: customOut });

    expect(existsSync(customOut)).toBe(true);
  });

  it('fails gracefully without essence file', async () => {
    rmSync(join(tmpDir, 'decantr.essence.json'));
    const origExitCode = process.exitCode;

    await cmdExport('shadcn', tmpDir);

    expect(process.exitCode).toBe(1);
    process.exitCode = origExitCode;
  });

  it('fails gracefully without tokens.css', async () => {
    rmSync(join(tmpDir, 'src', 'styles', 'tokens.css'));
    const origExitCode = process.exitCode;

    await cmdExport('shadcn', tmpDir);

    expect(process.exitCode).toBe(1);
    process.exitCode = origExitCode;
  });

  it('explains missing tokens as optional in contract-only Brownfield projects', async () => {
    rmSync(join(tmpDir, 'src', 'styles', 'tokens.css'));
    mkdirSync(join(tmpDir, '.decantr'), { recursive: true });
    writeFileSync(
      join(tmpDir, '.decantr', 'project.json'),
      JSON.stringify({
        initialized: {
          workflowMode: 'brownfield-attach',
          adoptionMode: 'contract-only',
        },
      }),
      'utf-8',
    );
    const origExitCode = process.exitCode;
    const errors: string[] = [];
    const spy = vi.spyOn(console, 'error').mockImplementation((message) => {
      errors.push(String(message));
    });

    await cmdExport('figma-tokens', tmpDir);

    spy.mockRestore();
    expect(process.exitCode).toBe(1);
    expect(errors.join('\n')).toContain('contract-only Brownfield');
    expect(errors.join('\n')).toContain('own Tailwind/Sass token system');
    expect(errors.join('\n')).toContain('decantr export --to figma-tokens');
    process.exitCode = origExitCode;
  });
});
