'use strict';

const Promise = require('bluebird');
const merge = require('object-merge-advanced');

const argv = require('./argv');
const include = require('./utils/argv-include')(argv);
const { resolveScraper, resolveLayoutScraper } = require('./scrapers/resolve');
const { fetchReleaseEntities, urlToSiteSlug } = require('./entities');
const logger = require('./logger')(__filename);
const qu = require('./utils/qu');

function toBaseReleases(baseReleasesOrUrls, entity = null) {
	if (!baseReleasesOrUrls) {
		return [];
	}

	return baseReleasesOrUrls
		.map((baseReleaseOrUrl) => {
			if (baseReleaseOrUrl.url) {
				// base release with URL
				return {
					...baseReleaseOrUrl,
					entity: baseReleaseOrUrl.entity || entity,
					deep: false,
				};
			}

			if (/^http/.test(baseReleaseOrUrl)) {
				// URL
				return {
					url: baseReleaseOrUrl,
					entity,
					deep: false,
				};
			}

			if (typeof baseReleaseOrUrl === 'object' && !Array.isArray(baseReleaseOrUrl)) {
				// base release without URL, prepare for passthrough
				return {
					...baseReleaseOrUrl,
					entity: baseReleaseOrUrl.entity || entity,
					deep: false,
				};
			}

			logger.warn(`Malformed base release, discarding '${baseReleaseOrUrl}'`);
			return null;
		})
		.filter(Boolean);
}

async function fetchScene(scraper, url, entity, baseRelease, options) {
	if (scraper.fetchScene) {
		return scraper.fetchScene(baseRelease.url, entity, baseRelease, options, null);
	}

	if (scraper.scrapeScene) {
		const session = qu.session();
		const res = await qu.get(url, null, null, { session });
		const cookie = await session._sessionOptions.cookieJar.get(url);

		if (res.ok) {
			return scraper.scrapeScene(res.item, url, entity, baseRelease, options, {
				session,
				headers: res.headers,
				cookieJar: session._sessionOptions.cookieJar,
				cookie,
			});
		}

		return res.status;
	}

	return null;
}

async function scrapeRelease(baseRelease, entitiesBySlug, type = 'scene') {
	const entity = baseRelease.entity || entitiesBySlug[urlToSiteSlug(baseRelease.url)];

	if (!entity) {
		logger.warn(`No entity available for ${baseRelease.url}`);
		return baseRelease;
	}

	if ((!baseRelease.url && !baseRelease.path) || !argv.deep) {
		return {
			...baseRelease,
			entity,
		};
	}

	const scraper = resolveScraper(entity);
	const layoutScraper = resolveLayoutScraper(entity, scraper);

	if (!layoutScraper) {
		logger.warn(`Could not find scraper for ${baseRelease.url}`);
		return baseRelease;
	}

	if ((type === 'scene' && !layoutScraper.fetchScene && !layoutScraper.scrapeScene) || (type === 'movie' && !layoutScraper.fetchMovie)) {
		logger.warn(`The '${entity.name}'-scraper cannot scrape individual ${type}s`);
		return baseRelease;
	}

	try {
		logger.verbose(`Fetching ${type} ${baseRelease.url}`);

		const scrapedRelease = type === 'scene'
			? await fetchScene(layoutScraper, baseRelease.url, entity, baseRelease, include, null)
			: await layoutScraper.fetchMovie(baseRelease.url, entity, baseRelease, include, null);

		if (typeof scrapedRelease !== 'object' || Array.isArray(scrapedRelease)) {
			// scraper is unable to fetch the releases and returned a HTTP code or null
			throw new Error(`Scraper returned ${scrapedRelease} when fetching latest from '${entity.name}' (${entity.parent?.name})`);
		}

		// object-merge-advance will use null as explicit false on hard merged keys, even when null as explicit falls is disabled
		// filter out keys with null values to ensure original base value is used instead
		const curatedScrapedRelease = Object.entries(scrapedRelease).reduce((acc, [key, value]) => ({
			...acc,
			...(value !== null && value !== undefined && {
				[key]: value,
			}),
		}), {});

		const mergedRelease = {
			...merge(baseRelease, curatedScrapedRelease, {
				dedupeStringsInArrayValues: true,
				hardMergeKeys: ['actors', 'covers', 'poster', 'trailer', 'teaser'],
			}),
			deep: !!scrapedRelease,
			entity,
		};

		if (!mergedRelease.entryId) {
			throw Object.assign(new Error('No entry ID supplied'), { code: 'NO_ENTRY_ID' });
		}

		if (scrapedRelease && baseRelease?.tags) {
			// accumulate all available tags
			mergedRelease.tags = baseRelease.tags.concat(scrapedRelease.tags);
		}

		return mergedRelease;
	} catch (error) {
		logger.error(`Deep scrape failed for ${baseRelease.url}: ${error.message}`);

		if (argv.debug) {
			console.error(error);
		}

		if (error.code === 'NO_ENTRY_ID') {
			return null;
		}

		return baseRelease;
	}
}

async function scrapeReleases(baseReleases, entitiesBySlug, type) {
	return Promise.map(
		baseReleases,
		async baseRelease => scrapeRelease(baseRelease, entitiesBySlug, type),
		{ concurrency: 10 },
	);
}

async function fetchReleases(baseReleasesOrUrls, type = 'scene') {
	const baseReleases = toBaseReleases(baseReleasesOrUrls);
	const entitiesBySlug = await fetchReleaseEntities(baseReleases);

	const deepReleases = await scrapeReleases(baseReleases, entitiesBySlug, type);

	return deepReleases.filter(Boolean);
}

async function fetchScenes(baseReleasesOrUrls) {
	return fetchReleases(baseReleasesOrUrls, 'scene');
}

async function fetchMovies(baseReleasesOrUrls) {
	const movies = await fetchReleases(baseReleasesOrUrls, 'movie');

	return movies;
}

module.exports = {
	fetchReleases,
	fetchScenes,
	fetchMovies,
	toBaseReleases,
};