import * as fs from 'fs';
import * as path from 'path';
import glob from 'tiny-glob/sync.js';

import {
	assert,
	showOutput,
	loadConfig,
	loadSvelte,
	setupHtmlEqual,
	tryToLoadJson,
	cleanRequireCache,
	shouldUpdateExpected,
	mkdirp
} from '../helpers';
import { set_current_component } from '../../internal';

function tryToReadFile(file) {
	try {
		return fs.readFileSync(file, 'utf-8');
	} catch (err) {
		if (err.code !== 'ENOENT') throw err;
		return null;
	}
}

const sveltePath = process.cwd().split('\\').join('/');
let compile = null;

describe('ssr', () => {
	before(() => {
		compile = loadSvelte(true).compile;

		return setupHtmlEqual();
	});

	let saved_window;
	before(() => saved_window = global.window);
	after(() => global.window = saved_window);

	fs.readdirSync(`${__dirname}/samples`).forEach(dir => {
		if (dir[0] === '.') return;

		const config = loadConfig(`${__dirname}/samples/${dir}/_config.js`);

		// add .solo to a sample directory name to only run that test, or
		// .show to always show the output. or both
		const solo = config.solo || /\.solo/.test(dir);
		const show = /\.show/.test(dir);

		if (solo && process.env.CI) {
			throw new Error('Forgot to remove `solo: true` from test');
		}

		(solo ? it.only : it)(dir, () => {
			dir = path.resolve(`${__dirname}/samples`, dir);

			cleanRequireCache();

			const compileOptions = {
				sveltePath,
				...config.compileOptions,
				generate: 'ssr',
				format: 'cjs'
			};

			require('../../register')(compileOptions);

			try {
				const Component = require(`${dir}/main.svelte`).default;

				const expectedHtml = tryToReadFile(`${dir}/_expected.html`);
				const expectedCss = tryToReadFile(`${dir}/_expected.css`) || '';

				const props = tryToLoadJson(`${dir}/data.json`) || undefined;

				const rendered = Component.render(props);
				const { html, css, head } = rendered;

				fs.writeFileSync(`${dir}/_actual.html`, html);
				if (css.code) fs.writeFileSync(`${dir}/_actual.css`, css.code);

				try {
					(compileOptions.preserveComments
						? assert.htmlEqualWithComments
						: assert.htmlEqual)(html, expectedHtml);
				} catch (error) {
					if (shouldUpdateExpected()) {
						fs.writeFileSync(`${dir}/_expected.html`, html);
						console.log(`Updated ${dir}/_expected.html.`);
					} else {
						throw error;
					}
				}

				try {
					assert.equal(
						css.code.replace(/^\s+/gm, '').replace(/[\r\n]/g, ''),
						expectedCss.replace(/^\s+/gm, '').replace(/[\r\n]/g, '')
					);
				} catch (error) {
					if (shouldUpdateExpected()) {
						fs.writeFileSync(`${dir}/_expected.css`, css.code);
						console.log(`Updated ${dir}/_expected.css.`);
					} else {
						throw error;
					}
				}

				if (fs.existsSync(`${dir}/_expected-head.html`)) {
					fs.writeFileSync(`${dir}/_actual-head.html`, head);

					try {
						assert.htmlEqual(
							head,
							fs.readFileSync(`${dir}/_expected-head.html`, 'utf-8')
						);
					} catch (error) {
						if (shouldUpdateExpected()) {
							fs.writeFileSync(`${dir}/_expected-head.html`, head);
							console.log(`Updated ${dir}/_expected-head.html.`);
						} else {
							throw error;
						}
					}
				}

				if (show) showOutput(dir, { generate: 'ssr', format: 'cjs' });
			} catch (err) {
				showOutput(dir, { generate: 'ssr', format: 'cjs' });
				err.stack += `\n\ncmd-click: ${path.relative(process.cwd(), dir)}/main.svelte`;
				throw err;
			} finally {
				set_current_component(null);
			}
		});
	});

	// duplicate client-side tests, as far as possible
	runRuntimeSamples('runtime');
	runRuntimeSamples('runtime-puppeteer');

	function runRuntimeSamples(suite) {
		fs.readdirSync(`test/${suite}/samples`).forEach(dir => {
			if (dir[0] === '.') return;

			const config = loadConfig(`./${suite}/samples/${dir}/_config.js`);
			const solo = config.solo || /\.solo/.test(dir);

			if (solo && process.env.CI) {
				throw new Error('Forgot to remove `solo: true` from test');
			}

			if (config.skip_if_ssr) return;

			(config.skip ? it.skip : solo ? it.only : it)(dir, () => {
				const cwd = path.resolve(`test/${suite}/samples`, dir);

				cleanRequireCache();

				delete global.window;

				const compileOptions = {
					sveltePath,
					...config.compileOptions,
					generate: 'ssr',
					format: 'cjs'
				};

				require('../../register')(compileOptions);

				glob('**/*.svelte', { cwd }).forEach(file => {
					if (file[0] === '_') return;

					const dir  = `${cwd}/_output/ssr`;
					const out = `${dir}/${file.replace(/\.svelte$/, '.js')}`;

					if (fs.existsSync(out)) {
						fs.unlinkSync(out);
					}

					mkdirp(dir);

					try {
						const { js } = compile(
							fs.readFileSync(`${cwd}/${file}`, 'utf-8'),
							{
								...compileOptions,
								filename: file
							}
						);

						fs.writeFileSync(out, js.code);
					} catch (err) {
						// do nothing
					}
				});

				try {
					if (config.before_test) config.before_test();

					const Component = require(`../${suite}/samples/${dir}/main.svelte`).default;
					const { html } = Component.render(config.props, {
						store: (config.store !== true) && config.store
					});

					if (config.ssrHtml) {
						assert.htmlEqual(html, config.ssrHtml);
					} else if (config.html) {
						assert.htmlEqual(html, config.html);
					}

					if (config.test_ssr) {
						config.test_ssr({ assert });
					}

					if (config.after_test) config.after_test();

					if (config.show) {
						showOutput(cwd, compileOptions);
					}
				} catch (err) {
					err.stack += `\n\ncmd-click: ${path.relative(process.cwd(), cwd)}/main.svelte`;

					if (config.error) {
						if (typeof config.error === 'function') {
							config.error(assert, err);
						} else {
							assert.equal(err.message, config.error);
						}
					} else {
						showOutput(cwd, compileOptions);
						throw err;
					}
				} finally {
					set_current_component(null);
				}
			});
		});
	}
});