import { Logger } from '@refloat/logger'
import { createServer } from 'http'
import fetch from 'cross-fetch'

import { ModuleReader } from './fetch'

const logger = Logger.create('module-server')

export async function bootModuleServer(port = 9090) {
	const reader = new ModuleReader()
	const server = createServer((req, res) => {
		const uri = req.url?.slice(1)

		if (uri) {
			const query = new URLSearchParams(uri)
			const checkoutPath = query.get('checkout')

			if (checkoutPath) {
				logger.verbose('checking out module file', { file: checkoutPath })

				return reader
					.checkout(checkoutPath)
					.then((contents) => res.end(contents))
					.catch((error) => {
						console.error(error)

						res.statusCode = 500
						res.end()
					})
			}

			const readFile = query.get('read')

			if (readFile) {
				logger.verbose('reading module file', { file: readFile })

				return reader
					.read(readFile)
					.then((contents) => {
						if (contents === null) {
							res.statusCode = 404
							res.end(`file "${readFile}" not found`)
						} else {
							res.end(contents)
						}
					})
					.catch((error) => {
						console.error(error)

						res.statusCode = 500
						res.end(String(error))
					})
			}

			const shaFile = query.get('sha')

			if (shaFile) {
				logger.verbose('reading module SHA', { file: shaFile })

				return reader
					.sha(shaFile)
					.then((sha) => res.end(sha))
					.catch((error) => {
						console.error(error)

						res.statusCode = 500
						res.end()
					})
			}

			res.statusCode = 404
			res.end()
		}
	})

	const listening = await new Promise<boolean>((resolve, reject) =>
		server
			.listen(port, () => resolve(true))
			.once('error', (error: any) =>
				error?.['code'] === 'EADDRINUSE' ? resolve(false) : reject(error),
			),
	)

	if (listening) {
		logger.verbose('module server listening', { port })
	}

	const get = client(port)

	return {
		close: () =>
			new Promise<void>((resolve, reject) =>
				listening
					? server.close((error) => (error ? reject(error) : resolve()))
					: resolve(),
			),
		checkout: async (source: string) => get('checkout', source),
		sha: async (file: string) => get('sha', file),
		get: async (file: string) => get('read', file, null),
	}
}

function client(port: number) {
	return async function get<T = string>(
		name: string,
		query: string,
		on404?: T,
	) {
		const res = await fetch(
			`http://127.0.0.1:${port}?${name}=${encodeURIComponent(query)}`,
		)
		const text = await res.text()

		if (res.status === 200) {
			return text
		} else if (res.status === 404 && typeof on404 !== 'undefined') {
			return on404
		} else {
			throw new Error(text.trim() || `HTTP error ${res.status}`)
		}
	}
}
