import { describe, test } from 'node:test';
import assert from 'node:assert/strict';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { execFileSync } from 'node:child_process';
import {
  TARGETS,
  DEFAULT_TOKENS,
  deriveDimensions,
  loadBrandTokens,
  tokensToCss,
  wrapSvg,
  injectTokens,
  parseSvgMeta,
  validateFigure,
  resolveChrome,
  parseArgs,
  renderFigure,
} from '../build-figur.mjs';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const FIXTURE_SVG = path.join(__dirname, 'fixtures', 'demo-figur.svg');

function tmpdir() {
  return fs.mkdtempSync(path.join(os.tmpdir(), 'build-figur-test-'));
}

// Leser bredde/høyde fra PNG-IHDR (byte 16–23, big-endian).
function pngDimensions(file) {
  const buf = fs.readFileSync(file);
  return { width: buf.readUInt32BE(16), height: buf.readUInt32BE(20) };
}

describe('TARGETS + deriveDimensions', () => {
  test('carousel er 1080×1350 (fast)', () => {
    assert.equal(TARGETS.carousel.width, 1080);
    assert.equal(TARGETS.carousel.height, 1350);
  });

  test('article er 1200 bred med innholdsstyrt høyde', () => {
    assert.equal(TARGETS.article.width, 1200);
    assert.equal(TARGETS.article.height, null);
  });

  test('single er kvadratisk 1200×1200', () => {
    assert.equal(TARGETS.single.width, 1200);
    assert.equal(TARGETS.single.height, 1200);
  });

  test('article + aspekt 16:9 gir avledet høyde 675', () => {
    const d = deriveDimensions('article', 1200 / 675, {});
    assert.equal(d.width, 1200);
    assert.equal(d.height, 675);
    assert.equal(d.warnings.length, 0);
  });

  test('article uten aspekt gir fallback-høyde med warning', () => {
    const d = deriveDimensions('article', null, {});
    assert.equal(d.width, 1200);
    assert.ok(d.height > 0);
    assert.ok(d.warnings.some((w) => /høyde/i.test(w)));
  });

  test('--width/--height-overstyringer vinner over målet', () => {
    const d = deriveDimensions('carousel', null, { width: 800, height: 600 });
    assert.equal(d.width, 800);
    assert.equal(d.height, 600);
  });

  test('ukjent mål kaster med gyldige mål i meldingen', () => {
    assert.throws(() => deriveDimensions('poster', null, {}), /article/);
  });
});

describe('loadBrandTokens — token-seam (brukerdata, aldri hardkodet)', () => {
  test('uten brand-tokens.json: nøytrale defaults, source=default', () => {
    const dir = tmpdir();
    const r = loadBrandTokens(dir);
    assert.equal(r.source, 'default');
    assert.deepEqual(r.tokens, DEFAULT_TOKENS);
    assert.equal(r.warnings.length, 0);
  });

  test('gyldig fil merges over defaults, source=user', () => {
    const dir = tmpdir();
    fs.mkdirSync(path.join(dir, 'profile'), { recursive: true });
    fs.writeFileSync(
      path.join(dir, 'profile', 'brand-tokens.json'),
      JSON.stringify({ accent: '#123456' }),
      'utf8'
    );
    const r = loadBrandTokens(dir);
    assert.equal(r.source, 'user');
    assert.equal(r.tokens.accent, '#123456');
    // umerkede felter beholder defaults
    assert.equal(r.tokens.background, DEFAULT_TOKENS.background);
  });

  test('ugyldig JSON: defaults + warning, kaster aldri', () => {
    const dir = tmpdir();
    fs.mkdirSync(path.join(dir, 'profile'), { recursive: true });
    fs.writeFileSync(path.join(dir, 'profile', 'brand-tokens.json'), '{not json', 'utf8');
    let r;
    assert.doesNotThrow(() => { r = loadBrandTokens(dir); });
    assert.equal(r.source, 'default');
    assert.ok(r.warnings.some((w) => /brand-tokens\.json/.test(w)));
  });
});

describe('tokensToCss + wrapSvg + injectTokens — CSS-injeksjon', () => {
  test('tokensToCss produserer --figur-variabler for alle tokens', () => {
    const css = tokensToCss(DEFAULT_TOKENS);
    assert.match(css, /:root\{/);
    for (const key of Object.keys(DEFAULT_TOKENS)) {
      assert.ok(css.includes(`--figur-${key}`), `mangler --figur-${key}`);
    }
  });

  test('egendefinerte tokenverdier reflekteres i CSS-en', () => {
    const css = tokensToCss({ ...DEFAULT_TOKENS, accent: '#ABCDEF' });
    assert.match(css, /--figur-accent:\s*#ABCDEF/);
  });

  test('wrapSvg bygger komplett HTML med svg + tokens + mål-dimensjoner', () => {
    const html = wrapSvg('<svg viewBox="0 0 10 10"></svg>', { width: 1080, height: 1350 }, DEFAULT_TOKENS);
    assert.match(html, /<!DOCTYPE html>/i);
    assert.match(html, /<svg viewBox="0 0 10 10">/);
    assert.match(html, /--figur-accent/);
    assert.match(html, /width:\s*1080px/);
    assert.match(html, /height:\s*1350px/);
  });

  test('injectTokens legger style rett etter <head> når den finnes', () => {
    const out = injectTokens('<html><head><title>x</title></head><body></body></html>', DEFAULT_TOKENS);
    assert.match(out, /<head>\s*<style>[^<]*--figur-ink/);
  });

  test('injectTokens prepender style når <head> mangler', () => {
    const out = injectTokens('<p>bare et fragment</p>', DEFAULT_TOKENS);
    assert.ok(out.trimStart().startsWith('<style>'));
    assert.match(out, /<p>bare et fragment<\/p>/);
  });
});

describe('parseSvgMeta — aspekt fra SVG-kilden', () => {
  test('viewBox gir aspekt', () => {
    const aspect = parseSvgMeta('<svg viewBox="0 0 1200 675"></svg>');
    assert.ok(Math.abs(aspect - 1200 / 675) < 1e-9);
  });

  test('width/height-attributter gir aspekt', () => {
    const aspect = parseSvgMeta('<svg width="800px" height="400"></svg>');
    assert.equal(aspect, 2);
  });

  test('uten dimensjonsinfo: null', () => {
    assert.equal(parseSvgMeta('<svg><rect/></svg>'), null);
  });
});

describe('validateFigure — designregler som warn, aldri hard fail', () => {
  test('font-size under 24 gir warning', () => {
    const w = validateFigure('<svg><text font-size="14">liten</text></svg>');
    assert.ok(w.some((x) => /24/.test(x)));
  });

  test('font-size ≥ 24 gir ingen tekst-warning', () => {
    const w = validateFigure('<svg><text font-size="28">ok</text><text style="font-size: 32px">ok</text></svg>');
    assert.ok(!w.some((x) => /24/.test(x)));
  });

  test('mer enn 2 ikke-nøytrale farger gir fargebudsjett-warning', () => {
    const w = validateFigure('<svg><rect fill="#9A3324"/><rect fill="#2F6F9F"/><rect fill="#22AA55"/></svg>');
    assert.ok(w.some((x) => /farge/i.test(x)));
  });

  test('nøytraler (gråtoner/hvit/sort) telles ikke mot fargebudsjettet', () => {
    const w = validateFigure(
      '<svg><rect fill="#FFFFFF"/><rect fill="#1A1A1A"/><rect fill="#888888"/><rect fill="#9A3324" font-size="30"/></svg>'
    );
    assert.ok(!w.some((x) => /farge/i.test(x)));
  });
});

describe('resolveChrome — robust binær-oppdagelse (mockbar)', () => {
  test('finner app-bundle-sti via exists-probe', () => {
    const r = resolveChrome({
      exists: (p) => p.includes('Google Chrome.app'),
      which: () => null,
    });
    assert.equal(r.available, true);
    assert.match(r.path, /Google Chrome/);
  });

  test('faller tilbake til PATH via which-probe', () => {
    const r = resolveChrome({
      exists: () => false,
      which: (cmd) => (cmd === 'chromium' ? '/usr/local/bin/chromium' : null),
    });
    assert.equal(r.available, true);
    assert.equal(r.path, '/usr/local/bin/chromium');
  });

  test('ingen kandidater: available=false med install-hint', () => {
    const r = resolveChrome({ exists: () => false, which: () => null });
    assert.equal(r.available, false);
    assert.match(r.hint, /Chrome/);
  });
});

describe('parseArgs — CLI-kontrakt', () => {
  test('defaults: target=article, out avledes senere (null)', () => {
    const r = parseArgs(['fig.svg']);
    assert.equal(r.error, undefined);
    assert.equal(r.input, 'fig.svg');
    assert.equal(r.target, 'article');
    assert.equal(r.out, null);
  });

  test('--target/--out/--width/--height parses (tall som tall)', () => {
    const r = parseArgs(['fig.svg', '--target', 'carousel', '--out', '/tmp/x.png', '--width', '900', '--height', '1200']);
    assert.equal(r.target, 'carousel');
    assert.equal(r.out, '/tmp/x.png');
    assert.equal(r.width, 900);
    assert.equal(r.height, 1200);
  });

  test('ugyldig target gir error med gyldige mål', () => {
    const r = parseArgs(['fig.svg', '--target', 'poster']);
    assert.match(r.error, /article/);
  });

  test('manglende input gir usage-error', () => {
    const r = parseArgs([]);
    assert.match(r.error, /[Bb]ruk/);
  });
});

// ---------------------------------------------------------------------------
// Ende-til-ende (krever ekte Chrome — skippes CI-vennlig når binær mangler)
// ---------------------------------------------------------------------------
const chrome = resolveChrome();

describe('renderFigure — ende-til-ende via headless Chrome', { skip: !chrome.available && 'Chrome ikke funnet' }, () => {
  test('article: fixture-SVG → PNG med 1200px bredde og aspekt-avledet høyde', async () => {
    const out = path.join(tmpdir(), 'fig-article.png');
    const r = await renderFigure({ input: FIXTURE_SVG, target: 'article', out, dataDir: tmpdir() });
    assert.equal(r.out, out);
    const dim = pngDimensions(out);
    assert.equal(dim.width, 1200);
    assert.equal(dim.height, 675); // fixture er 16:9 (viewBox 1200×675)
  });

  test('carousel: fixture-SVG → PNG 1080×1350', async () => {
    const out = path.join(tmpdir(), 'fig-carousel.png');
    await renderFigure({ input: FIXTURE_SVG, target: 'carousel', out, dataDir: tmpdir() });
    const dim = pngDimensions(out);
    assert.equal(dim.width, 1080);
    assert.equal(dim.height, 1350);
  });

  test('render uten brand-tokens.json lykkes med nøytrale defaults', async () => {
    const out = path.join(tmpdir(), 'fig-default-tokens.png');
    const r = await renderFigure({ input: FIXTURE_SVG, target: 'single', out, dataDir: tmpdir() });
    assert.equal(r.tokensSource, 'default');
    assert.ok(fs.existsSync(out));
    // PNG-magic
    const buf = fs.readFileSync(out);
    assert.equal(buf.readUInt32BE(0), 0x89504e47);
  });

  test('CLI: node build-figur.mjs <fixture> --target carousel --out <png>', () => {
    const out = path.join(tmpdir(), 'fig-cli.png');
    const script = path.join(__dirname, '..', 'build-figur.mjs');
    execFileSync(process.execPath, [script, FIXTURE_SVG, '--target', 'carousel', '--out', out], {
      stdio: ['ignore', 'pipe', 'pipe'],
    });
    const dim = pngDimensions(out);
    assert.equal(dim.width, 1080);
    assert.equal(dim.height, 1350);
  });
});
