import { Logger } from '@refloat/logger'

import { APIClient } from '../api/api-client'
import { Repo, RepoProviderType } from '../generated/schema'
import { runConfig } from '../modules/run'
import { bootModuleServer } from '../modules/server'
import { AbortContext } from '../utils/abort-context'
import { error } from '../utils/error'
import { sleep } from '../utils/sleep'

const logger = Logger.create('config-agent')

export async function configAgent(ctx: AbortContext, api: APIClient) {
	const modules = await bootModuleServer()

	try {
		while (ctx.running) {
			const build = await api.acquireBuild()

			if (!build) {
				await ctx.wait(sleep(1000))

				continue
			}

			let sha: string | null = null

			try {
				logger.info('compiling configuration', { build: build.path })

				const start = Date.now()
				const overrides: Record<string, string> = {}
				const path = `${origin(
					build.repo,
					build.sha === 'HEAD' ? build.branch : build.sha,
				)}/.refloat/config.js`

				await modules.checkout(path)

				const [configSha, configSource] = await Promise.all([
					modules.sha(path),
					modules.get(path),
				])

				sha = configSha

				if (!configSource) {
					logger.warn('no config file found', { build: build.path })

					await api.configureBuildError(
						build.id,
						'No configuration file was found on .refloat/config.js',
						{ sha, notFound: true },
					)

					continue
				}

				if (build.dependencyOf) {
					overrides[origin(build.dependencyOf.repo)] = origin(
						build.dependencyOf.repo,
						build.dependencyOf.sha,
					)
				}

				const { dependencies, config } = await runConfig(
					path,
					configSource,
					modules.checkout,
					async (file) => {
						const contents = await modules.get(file)

						if (contents === null) {
							throw new Error(`file "${file}" not found`)
						} else {
							return contents
						}
					},
					{
						org: {
							id: build.repo.org.id,
							slug: build.repo.org.slug,
						},
						user: {
							id: build.user.id,
							displayName: build.user.displayName,
						},
						repo: {
							id: build.repo.id,
							slug: build.repo.slug,
							defaultBranch: build.repo.defaultBranch,
						},
						build: {
							id: build.id,
							number: build.number,
						},
						commit: {
							sha,
							branch: build.branch,
							defaultBranch: build.branch === build.repo.defaultBranch,
						},
					},
					overrides,
				)

				logger.info('configuration compiled', {
					build: build.path,
					duration: `${Date.now() - start}ms`,
				})

				await api.configureBuild(build.id, sha, dependencies, config)
			} catch (error: any) {
				error = error?.networkError?.result ?? error ?? 'Undefined error'

				logger.error('config job error', { error })

				try {
					await api.configureBuildError(
						build.id,
						String(error?.message ?? error),
						{ sha },
					)
				} catch (error) {
					logger.error('failed to notify server about config error', { error })
				}
			}
		}
	} finally {
		await modules.close()
	}
}

const origin = (
	repo: Pick<Repo, 'providerType' | 'providerSource'>,
	sha?: string,
) =>
	`${
		repo.providerType === RepoProviderType.GitHub
			? `git+ssh://github.com/${repo.providerSource}`
			: repo.providerType === RepoProviderType.Bitbucket
			? `git+ssh://bitbucket.org/${repo.providerSource}`
			: repo.providerType === RepoProviderType.Custom
			? httpsRe.test(repo.providerSource)
				? `git+https://${repo.providerSource.replace(httpsRe, '')}`
				: error(`unsupported repository source: "${repo.providerSource}"`)
			: error(`unexpected providerType: "${repo.providerType}"`)
	}${sha ? '#' + sha : ''}`

const httpsRe = /^https:\/\//i
