import isRoughlyEqual from 'is-roughly-equal'
import {ok, AssertionError} from 'assert'
import {DateTime} from 'luxon'
import * as a from 'assert'
import {createRequire} from 'module'
import {gunzipSync} from 'zlib'

const hour = 60 * 60 * 1000
const day = 24 * hour
const week = 7 * day

// next Monday 10 am
const createWhen = (timezone, locale, tMock) => {
	ok(Number.isInteger(tMock), 'tMock must be an integer')

	const t = process.env.VCR_MODE && !process.env.VCR_OFF
		? tMock
		: Date.now()
	return DateTime.fromMillis(t, {
		zone: timezone,
		locale,
	}).startOf('week').plus({weeks: 1, hours: 10}).toJSDate()
}

const assertValidWhen = (actual, expected, name, delta = day + 6 * hour) => {
	const ts = +new Date(actual)
	a.ok(!Number.isNaN(ts), name + ' is not parsable by Date')
	// the timestamps might be from long-distance trains
	if (!isRoughlyEqual(delta, +expected, ts)) {
		throw new AssertionError({
			message: name + ' is out of range',
			actual: ts,
			expected: `${expected - delta} - ${+expected + delta}`,
			operator: 'isRoughlyEqual',
		})
	}
}

// HTTP request mocking
if (process.env.VCR_MODE && !process.env.VCR_OFF) {
	const require = createRequire(import.meta.url)

	const {Polly} = require('@pollyjs/core')
	const NodeHttpAdapter = require('@pollyjs/adapter-node-http')
	const FSPersister = require('@pollyjs/persister-fs')
	const tap = require('tap')

	// monkey-patch NodeHttpAdapter to handle gzipped responses properly
	// todo: submit a PR
	// related: https://github.com/Netflix/pollyjs/issues/256
	// related: https://github.com/Netflix/pollyjs/issues/463
	// related: https://github.com/Netflix/pollyjs/issues/207
	const _getBodyFromChunks = NodeHttpAdapter.prototype.getBodyFromChunks
	NodeHttpAdapter.prototype.getBodyFromChunks = function getBodyFromChunksWithGunzip (chunks, headers) {
		if (headers['content-encoding'] === 'gzip') {
			const concatenated = Buffer.concat(chunks)
			chunks = [gunzipSync(concatenated)]
			// todo: this is ugly, find a better way
			delete headers['content-encoding']
			headers['content-length'] = chunks[0].length
		}
		return _getBodyFromChunks.call(this, chunks, headers)
	}

	Polly.register(NodeHttpAdapter)
	Polly.register(FSPersister)

	let mode
	if (process.env.VCR_MODE === 'record') mode = 'record'
	else if (process.env.VCR_MODE === 'playback') mode = 'replay'
	else throw new Error('invalid $VCR_MODE, must be "record" or "replay"')

	const polly = new Polly('requests', {
		logLevel: 'warn',
		// If a request's recording is not found, pass-through to the server and record the response.
		recordIfMissing: false,
		// If false, Polly will throw when attempting to persist any failed requests. A request is considered to be a failed request when its response's status code is ≥ 400.
		recordFailedRequests: true,
		// Await any unresolved requests handled by the polly instance (via flush) when stop is called.
		flushRequestsOnStop: true,
		// The Polly mode. Can be one of the following:
		// - replay: Replay responses from recordings.
		// - record: Force Polly to record all requests. This will overwrite recordings that already exist.
		// - passthrough: Passes all requests through directly to the server without recording or replaying.
		mode,
		adapters: ['node-http'],
		persister: 'fs',
		persisterOptions: {
			fs: {
				recordingsDir: new URL('../fixtures', import.meta.url).pathname,
			},
			// When disabled, requests that have not been captured by the running Polly instance will be removed from any previous recording. This ensures that a recording will only contain the requests that were made during the lifespan of the Polly instance. When enabled, new requests will be appended to the recording file.
			keepUnusedRequests: true, // todo: change to false?
		},
		matchRequestsBy: {
			order: false,
			headers: {
				// todo: use an allow-list here?
				exclude: [
					// request
					'user-agent', // randomised
					'connection', // not relevant for tests
					// response
					'set-cookie', // often randomised
					'date', // constantly changing
				],
			},
		},
	})

	tap.teardown(async () => {
		await polly.stop()
	})
}

export {
	hour, createWhen, assertValidWhen,
}
