import { Logger } from '@refloat/logger'
import { pipeline } from 'stream/promises'
import { dirname, resolve } from 'path'
import { createReadStream, createWriteStream } from 'fs'
import { mkdir } from 'fs/promises'
import fetch from 'node-fetch'
import glob from 'glob'
import tar from 'tar'

import { APIClient } from '../api/api-client'
import { Configuration } from '../config'

export interface Options {
	buildId: string
	name: string
	cwd?: string
}

export async function importWorkspace({
	name,
	buildId,
	cwd = process.cwd(),
}: Options) {
	const res = await fetch(url(`workspace/import/${buildId}`, name), {
		compress: false,
		headers: await getHeaders(),
	})

	if (res.status === 200) {
		await pipeline(
			res.body,
			tar.extract({
				cwd,
				onwarn: (message) => loggers.importWorkspace.warn(message),
				onentry: (entry) =>
					loggers.importWorkspace.info('extracting', { file: entry.path }),
			}),
		)
	} else {
		throw new Error(`HTTP Error ${res.status}: ${res.statusText}`)
	}
}

export async function importArtifact(
	name: string,
	{
		buildId,
		path = name,
		cwd = process.cwd(),
	}: { buildId: string; path?: string; cwd?: string },
) {
	const headers = await getHeaders()
	const res = await fetch(url(`artifact/import/${buildId}`, name), {
		headers,
		compress: true,
	})

	if (res.status === 200) {
		const dest = resolve(cwd, path)

		await mkdir(dirname(dest), { recursive: true })
		await pipeline(res.body, createWriteStream(dest))
	} else {
		throw new Error(`HTTP Error ${res.status}: ${res.statusText}`)
	}
}

export async function exportArtifact(
	patterns: string[],
	{
		stepId,
		name,
		cwd = process.cwd(),
	}: { stepId: string; name?: string; cwd?: string },
) {
	const [headers, files] = await Promise.all([
		getHeaders(),
		Promise.all(
			patterns.map((pattern) =>
				matchGlob(pattern, cwd, loggers.exportArtifact),
			),
		).then((files) => files.flat()),
	])

	if (name && files.length > 1) {
		throw new Error(
			`Artifact name provided but pattern matched more than one file`,
		)
	}

	await Promise.all(
		files.map(async (file) => {
			loggers.exportArtifact.info('sending', name ? { file, name } : { file })

			const res = await fetch(url(`artifact/export/${stepId}`, name ?? file), {
				headers,
				method: 'PUT',
				compress: true,
				body: createReadStream(file),
			})

			if (res.status !== 200) {
				throw new Error(`HTTP Error ${res.status}: ${res.statusText}`)
			}
		}),
	)
}

export async function exportWorkspace(
	patterns: string[],
	{ buildId, name, cwd = process.cwd() }: Options,
) {
	const [headers, files] = await Promise.all([
		getHeaders(),
		Promise.all(
			patterns.map((pattern) =>
				matchGlob(pattern, cwd, loggers.exportWorkspace),
			),
		),
	])

	await fetch(url('workspace/export/' + buildId, name), {
		headers,
		method: 'PUT',
		compress: false,
		body: tar.create(
			{
				cwd,
				gzip: true,
				onwarn: (message) => loggers.importWorkspace.warn(message),
				filter: (file) => {
					loggers.exportWorkspace.info('archiving', { file })

					return true
				},
			},
			files.flat(),
		),
	})
}

async function getToken() {
	const config = Configuration.get()
	const api = new APIClient(config.api)
	const { token } = await api.register(config)

	return token
}

async function matchGlob(pattern: string, cwd: string, logger: Logger) {
	return new Promise<string[]>((resolve, reject) =>
		glob(pattern, { cwd }, (error, matches) => {
			if (error) {
				reject(error)
			} else if (matches.length) {
				logger.debug('pattern matches', { pattern, matches })

				resolve(matches)
			} else {
				throw new Error(`no matches found for "${pattern}"`)
			}
		}),
	)
}

async function getHeaders() {
	return {
		Authorization: `Bearer ${await getToken()}`,
	}
}

function url(endpoint: string, name: string) {
	const config = Configuration.get()
	const url = new URL(config.api.href + 'api/agent/' + endpoint)

	url.searchParams.append('name', name)

	return url.href
}

const loggers = {
	importWorkspace: Logger.create('import-workspace'),
	exportWorkspace: Logger.create('export-workspace'),
	exportArtifact: Logger.create('export-artifact'),
}
