import { HOST_DISABLED, PAGE_NOT_FOUND_ERROR } from "../../../constants/error-messages.js";
import { regEx } from "../../../util/regex.js";
import { addSecretForSanitizing } from "../../../util/sanitize.js";
import { logger } from "../../../logger/index.js";
import { ensureTrailingSlash, parseUrl, trimTrailingSlash } from "../../../util/url.js";
import { find } from "../../../util/host-rules.js";
import { detectPlatform } from "../../../util/common.js";
import { coerceArray } from "../../../util/array.js";
import { ExternalHostError } from "../../../types/errors/external-host-error.js";
import { memCacheProvider } from "../../../util/http/cache/memory-http-cache-provider.js";
import { toSha256 } from "../../../util/hash.js";
import api from "../../versioning/docker/index.js";
import { getGoogleAuthToken } from "../util.js";
import { parseGitUrl } from "../../../util/git/url.js";
import { ecrRegex, getECRAuthToken } from "./ecr.js";
import { googleRegex } from "./google.js";
import { isNonEmptyString, isString } from "@sindresorhus/is";
import { parse } from "auth-header";

//#region lib/modules/datasource/docker/common.ts
const dockerDatasourceId = "docker";
const imageUrlLabel = "org.opencontainers.image.url";
const sourceLabel = "org.opencontainers.image.source";
const sourceLabels = [sourceLabel, "org.label-schema.vcs-url"];
const gitRefLabel = "org.opencontainers.image.revision";
const DOCKER_HUB = "https://index.docker.io";
function isDockerHost(host) {
	return regEx(/(?:^|\.)docker\.io$/).test(host);
}
async function getAuthHeaders(http, registryHost, dockerRepository, apiCheckUrl = `${registryHost}/v2/`) {
	try {
		const options = {
			throwHttpErrors: false,
			noAuth: true,
			cacheProvider: memCacheProvider
		};
		const apiCheckResponse = apiCheckUrl.endsWith("/v2/") ? await http.get(apiCheckUrl, options) : await http.getJsonUnchecked(apiCheckUrl, options);
		if (apiCheckResponse.statusCode === 200) {
			logger.debug(`No registry auth required for ${apiCheckUrl}`);
			return {};
		}
		if (apiCheckResponse.statusCode === 404) {
			logger.debug(`Page Not Found ${apiCheckUrl}`);
			throw new Error(PAGE_NOT_FOUND_ERROR);
		}
		if (apiCheckResponse.statusCode !== 401 || !isNonEmptyString(apiCheckResponse.headers["www-authenticate"])) {
			logger.warn({
				apiCheckUrl,
				res: apiCheckResponse
			}, "Invalid registry response");
			return null;
		}
		const authenticateHeader = parse(apiCheckResponse.headers["www-authenticate"]);
		const rule = find({
			hostType: dockerDatasourceId,
			url: apiCheckUrl
		});
		const opts = {};
		if (ecrRegex.test(registryHost)) {
			logger.once.debug(`hostRules: ecr auth for ${registryHost}`);
			logger.trace({
				registryHost,
				dockerRepository
			}, `Using ecr auth for Docker registry`);
			const [, region] = coerceArray(ecrRegex.exec(registryHost));
			const auth = await getECRAuthToken(region, rule);
			if (auth) opts.headers = { authorization: `Basic ${auth}` };
		} else if (googleRegex.test(registryHost) && typeof rule.username === "undefined" && typeof rule.password === "undefined" && typeof rule.token === "undefined") {
			logger.once.debug(`hostRules: google auth for ${registryHost}`);
			logger.trace({
				registryHost,
				dockerRepository
			}, `Using google auth for Docker registry`);
			const auth = await getGoogleAuthToken();
			if (auth) opts.headers = { authorization: `Basic ${auth}` };
			else logger.once.debug({
				registryHost,
				dockerRepository
			}, "Could not get Google access token, using no auth");
		} else if (rule.username && rule.password) {
			logger.once.debug(`hostRules: basic auth for ${registryHost}`);
			logger.trace({
				registryHost,
				dockerRepository
			}, `Using basic auth for Docker registry`);
			opts.headers = { authorization: `Basic ${Buffer.from(`${rule.username}:${rule.password}`).toString("base64")}` };
		} else if (rule.token) {
			const authType = rule.authType ?? "Bearer";
			logger.once.debug(`hostRules: ${authType} token auth for ${registryHost}`);
			logger.trace({
				registryHost,
				dockerRepository
			}, `Using ${authType} token for Docker registry`);
			opts.headers = { authorization: `${authType} ${rule.token}` };
		}
		if (authenticateHeader.scheme.toUpperCase() !== "BEARER" || !isString(authenticateHeader.params.realm) || parseUrl(authenticateHeader.params.realm) === null) {
			logger.once.debug(`hostRules: testing direct auth for ${registryHost}`);
			logger.trace({
				registryHost,
				dockerRepository,
				authenticateHeader
			}, `Invalid realm, testing direct auth`);
			return opts.headers ?? null;
		}
		const authUrl = new URL(`${authenticateHeader.params.realm}`);
		if (isString(authenticateHeader.params.scope) && !apiCheckUrl.endsWith("/v2/")) authUrl.searchParams.append("scope", authenticateHeader.params.scope);
		else authUrl.searchParams.append("scope", `repository:${dockerRepository}:pull`);
		if (isString(authenticateHeader.params.service)) authUrl.searchParams.append("service", authenticateHeader.params.service);
		logger.trace({
			registryHost,
			dockerRepository,
			authUrl: authUrl.href
		}, `Obtaining docker registry token`);
		opts.noAuth = true;
		opts.cacheProvider = memCacheProvider;
		const authResponse = (await http.getJsonUnchecked(authUrl.href, opts)).body;
		const token = authResponse.token ?? authResponse.access_token;
		/* v8 ignore next 4 -- TODO: add test */
		if (!token) {
			logger.warn("Failed to obtain docker registry token");
			return null;
		}
		addSecretForSanitizing(token);
		return { authorization: `Bearer ${token}` };
	} catch (err) 	/* istanbul ignore next */ {
		/* v8 ignore if */
		if (err.host === "quay.io") return null;
		/* v8 ignore if */
		if (err.statusCode === 401) {
			logger.debug({
				registryHost,
				dockerRepository
			}, "Unauthorized docker lookup");
			logger.debug({ err });
			return null;
		}
		/* v8 ignore if */
		if (err.statusCode === 403) {
			logger.debug({
				registryHost,
				dockerRepository
			}, "Not allowed to access docker registry");
			logger.debug({ err });
			return null;
		}
		if (err.name === "RequestError" && isDockerHost(registryHost)) throw new ExternalHostError(err);
		/* v8 ignore if */
		if (err.statusCode === 429 && isDockerHost(registryHost)) throw new ExternalHostError(err);
		/* v8 ignore if */
		if (err.statusCode >= 500 && err.statusCode < 600) throw new ExternalHostError(err);
		if (err.message === PAGE_NOT_FOUND_ERROR) throw err;
		/* v8 ignore if */
		if (err.message === HOST_DISABLED) {
			logger.trace({
				registryHost,
				dockerRepository,
				err
			}, "Host disabled");
			return null;
		}
		logger.warn({
			registryHost,
			dockerRepository,
			err
		}, "Error obtaining docker token");
		return null;
	}
}
function getRegistryRepository(packageName, registryUrl) {
	if (registryUrl !== DOCKER_HUB) {
		const registryEndingWithSlash = ensureTrailingSlash(registryUrl.replace(regEx(/^https?:\/\//), ""));
		if (packageName.startsWith(registryEndingWithSlash)) {
			let registryHost = trimTrailingSlash(registryUrl);
			if (!regEx(/^https?:\/\//).test(registryHost)) registryHost = `https://${registryHost}`;
			let dockerRepository = packageName.replace(registryEndingWithSlash, "");
			const { origin, pathname } = parseUrl(`${registryHost}/${dockerRepository}`);
			registryHost = origin;
			dockerRepository = pathname.substring(1);
			return {
				registryHost,
				dockerRepository
			};
		}
	}
	let registryHost = registryUrl;
	const split = packageName.split("/");
	if (split.length > 1 && (split[0].includes(".") || split[0].includes(":"))) {
		[registryHost] = split;
		split.shift();
	}
	let dockerRepository = split.join("/");
	if (!regEx(/^https?:\/\//).test(registryHost)) registryHost = `https://${registryHost}`;
	const { path, base } = regEx(/^(?<base>https:\/\/[^/]+)\/(?<path>.+)$/).exec(registryHost)?.groups ?? {};
	if (base && path) {
		registryHost = base;
		dockerRepository = `${trimTrailingSlash(path)}/${dockerRepository}`;
	}
	registryHost = registryHost.replace("https://docker.io", "https://index.docker.io").replace("https://registry-1.docker.io", "https://index.docker.io");
	if (find({
		hostType: dockerDatasourceId,
		url: registryHost
	})?.insecureRegistry) registryHost = registryHost.replace("https", "http");
	if (registryHost.endsWith(".docker.io") && !dockerRepository.includes("/")) dockerRepository = "library/" + dockerRepository;
	return {
		registryHost,
		dockerRepository
	};
}
function extractDigestFromResponseBody(manifestResponse) {
	return "sha256:" + toSha256(manifestResponse.body);
}
function findLatestStable(tags) {
	let stable = null;
	for (const tag of tags) {
		if (!api.isValid(tag) || !api.isStable(tag)) continue;
		if (!stable || api.isGreaterThan(tag, stable)) stable = tag;
	}
	return stable;
}
const chartRepo = regEx(/charts?|helm|helm-charts?/i);
function isPossibleChartRepo(url) {
	if (detectPlatform(url) === null) return false;
	const parsed = parseGitUrl(url);
	return chartRepo.test(parsed.name);
}
function findHelmSourceUrl(release) {
	if (release.home && isPossibleChartRepo(release.home)) return release.home;
	if (!release.sources?.length) return null;
	for (const url of release.sources) if (isPossibleChartRepo(url)) return url;
	return release.sources[0];
}

//#endregion
export { DOCKER_HUB, dockerDatasourceId, extractDigestFromResponseBody, findHelmSourceUrl, findLatestStable, getAuthHeaders, getRegistryRepository, gitRefLabel, imageUrlLabel, isDockerHost, sourceLabel, sourceLabels };
//# sourceMappingURL=common.js.map