'use strict';

const {promisify} = require('util');
const {finished, Transform} = require('stream');

const fancyLog = require('fancy-log');
const git = require('./lib.js');
const inspectWithKind = require('inspect-with-kind');
const {isVinyl} = require('vinyl');
const PluginError = require('plugin-error');
const vinylFs = require('vinyl-fs');

/*
 * Public: Push to gh-pages branch for github
 *
 * options - {Object} that contains all the options of the plugin
 *   - remoteUrl: The {String} remote url (github repository) of the project,
 *   - branch: The {String} branch where deploy will by done (default to `"gh-pages"`),
 *   - cacheDir: {String} where the git repo will be located. (default to a temporary folder)
 *   - push: {Boolean} to know whether or not the branch should be pushed (default to `true`)
 *   - message: {String} commit message (default to `"Update [timestamp]"`)
 *
 * Returns `Stream`.
**/
module.exports = function gulpGhPages(options) {
	options = {...options};
	const branch = options.branch || 'gh-pages';
	const message = options.message || `Update ${new Date().toISOString()}`;

	const files = [];
	const TAG = branch === 'gh-pages' ? '[gh-pages]' : `[gh-pages (${branch})]`;

	return new Transform({
		objectMode: true,
		transform(file, enc, cb) {
			if (!isVinyl(file)) {
				if (file !== null && typeof file === 'object' && typeof file.isNull === 'function') {
					cb(new PluginError('gulp-gh-pages', 'gulp-gh-pages doesn\'t support gulp <= v3.x. Update your project to use gulp >= v4.0.0.'));
					return;
				}

				cb(new PluginError('gulp-gh-pages', `Expected a stream created by gulp-gh-pages to receive Vinyl objects https://github.com/gulpjs/vinyl, but got ${
					inspectWithKind(file)
				}.`));
				return;
			}

			if (file.isNull()) {
				cb(null, file);
				return;
			}

			if (file.isStream()) {
				cb(new PluginError('gulp-gh-pages', 'Stream content is not supported'));
				return;
			}

			files.push(file);
			cb(null, file);
		},
		async flush(cb) {
			if (files.length === 0) {
				fancyLog(TAG, 'No files in the stream.');
				cb();
				return;
			}

			try {
				const repo = await git.prepareRepo(options.remoteUrl, options.cacheDir || '.publish');
				fancyLog(TAG, 'Cloning repo');

				if (repo._localBranches.includes(branch)) {
					fancyLog(TAG, `Checkout branch \`${branch}\``);
					await repo.checkoutBranch(branch);
				}

				if (repo._remoteBranches.includes(`origin/${branch}`)) {
					fancyLog(TAG, `Checkout remote branch \`${branch}\``);
					await repo.checkoutBranch(branch);
					fancyLog(TAG, 'Updating repository');
					// updating to avoid having local cache not up to date
					await promisify(repo._repo.git.bind(repo._repo))('pull');
				} else {
					fancyLog(TAG, `Create branch \`${branch}\` and checkout`);
					await repo.createAndCheckoutBranch(branch);
				}

				await promisify(repo._repo.remove.bind(repo._repo))('.', {
					r: true,
					'ignore-unmatch': true
				});
				fancyLog(TAG, 'Copying files to repository');

				const destStream = vinylFs.dest(repo._repo.path);

				for (const file of files) {
					destStream.write(file);
				}

				setImmediate(() => destStream.end());
				await promisify(finished)(destStream);
				await repo.addFiles('.', {force: options.force || false});
				const filesToBeCommitted = Object.keys(repo._staged).length;

				if (filesToBeCommitted === 0) {
					fancyLog(TAG, 'No files have changed.');
					cb();
					return;
				}

				fancyLog(TAG, `Adding ${filesToBeCommitted} files.`);
				fancyLog(TAG, `Committing "${message}"`);

				const nrepo = await repo.commit(message);

				if (!(options.push === undefined || options.push)) {
					cb();
					return;
				}

				fancyLog(TAG, 'Pushing to remote.');
				await promisify(nrepo._repo.git.bind(nrepo._repo))('push', {
					'set-upstream': true
				}, ['origin', nrepo._currentBranch]);

				cb();
			} catch (err) {
				cb(err);
			}
		}
	});
};
