const assert = require('assert');
const fs = require('fs');
const Module = require('module');
const path = require('path');
const postcss = require('postcss');
const plugin = require('postcss-advanced-variables');
const scssSyntaxMarker = {};
const load = Module._load;

Module._load = function(request, parent, isMain) {
	if (request === 'postcss-scss') {
		return scssSyntaxMarker;
	}

	return load.apply(this, arguments);
};

const tests = require('../../../.tape.js');

Module._load = load;

const testDir = path.join(__dirname, '..', '..', '..', 'test');

function read(file) {
	return fs.readFileSync(path.join(testDir, file), 'utf8');
}

function normalize(css) {
	return css.trim().replace(/\r\n/g, '\n');
}

function scssSyntax() {
	return {
		parse(css, opts) {
			const sanitized = css
				.replace(/^([ \t]*)\/\/(.*)$/gm, '$1/*__PAV_SCSS_COMMENT__$2 */')
				.replace(/#\{\$([A-z][\w-]*)\}/g, '__PAV_INTERP_$1__');
			const root = postcss.parse(sanitized, opts);

			root.walkRules(rule => {
				rule.selector = rule.selector.replace(/__PAV_INTERP_([A-z][\w-]*)__/g, (_, name) => `#{$${name}}`);
			});
			root.walkDecls(decl => {
				decl.value = decl.value.replace(/__PAV_INTERP_([A-z][\w-]*)__/g, (_, name) => `#{$${name}}`);
			});

			return root;
		},
		stringify(node, builder) {
			postcss.stringify(node, (part, n, type) => {
				builder(
					typeof part === 'string'
						? part.replace(/\/\*__PAV_SCSS_COMMENT__(.*?) \*\//g, '//$1')
						: part,
					n,
					type
				);
			});
		}
	};
}

async function runTest(name, config) {
	const source = config.source || `${name.split(':')[0]}.css`;
	const expect = config.expect || `${name.replace(/:/g, '.')}.expect.css`;
	const processOptions = Object.assign({}, config.processOptions);

	if (processOptions.syntax === scssSyntaxMarker) {
		processOptions.syntax = scssSyntax();
	}

	if (!('from' in processOptions)) {
		processOptions.from = path.join(testDir, source);
	}

	const processor = postcss([plugin(config.options)]);

	if (config.error) {
		await assert.rejects(
			() => processor.process(read(source), processOptions),
			error => config.error.reason.test(error.reason || error.message)
		);
		return;
	}

	const result = await processor.process(read(source), processOptions);

	assert.strictEqual(normalize(result.css), normalize(read(expect)), name);

	if ('warnings' in config) {
		assert.strictEqual(result.warnings().length, config.warnings, name);
	}
}

(async () => {
	for (const [name, config] of Object.entries(tests)) {
		await runTest(name, config);
	}
})().catch(error => {
	console.error(error);
	process.exitCode = 1;
});
