import Docker from "dockerode";
import semver from "semver";
import { z } from "zod";
import { $ } from "zx";
import { sift } from "radash";

import { createTRPCRouter, publicProcedure } from "@/server/api/trpc";
import { TRPCError, type inferRouterOutputs } from "@trpc/server";

export const dockerRouter = createTRPCRouter({
	latest: publicProcedure.input(z.object({ id: z.string() })).query(async ({ input }) => {
		const docker = getDocker();
		const containers = await getContainers(docker);

		const container = containers.find((container) => container.Id === input.id);
		if (!container) {
			throw new TRPCError({
				code: "NOT_FOUND",
				message: `Container with id ${input.id} not found`,
			});
		}

		try {
			const imageData = await getImageData(docker, container);

			return {
				latest: await getLatest(imageData.name, imageData.tag),
			};
		} catch (ex) {
			throw new TRPCError({
				code: "INTERNAL_SERVER_ERROR",
				message: (ex as Error).message,
				cause: (ex as Error).cause,
			});
		}
	}),
	list: publicProcedure.query(async () => {
		const docker = getDocker();
		const containers = await getContainers(docker);

		// All this data should be local/from the local docker socket/api
		const dockerInfo = sift(
			await Promise.all(
				containers.map(async (container) => {
					try {
						if (!container.Id) {
							console.error("No container id could be found");
							throw new Error("No container id could be found");
						}

						const imageData = await getImageData(docker, container);

						const info = {
							container: {
								name: container.Names[0]?.replace("/", "") ?? "",
								id: container.Id,
							},
							image: {
								name: imageData.name,
								current: {
									hash: imageData.hash.substring(0, 12),
									tag: imageData.tag,
								},
							},
						};
						return info;
					} catch (ex) {
						console.error("Error getting container data:", {
							ex: {
								message: (ex as Error).message,
								cause: (ex as Error).cause,
							},
							container: {
								id: container.Id,
							},
						});
						return null;
					}
				}),
			),
		);

		if (dockerInfo.length === 0) {
			console.error("No docker containers could be found, check logs for more information");
			throw new TRPCError({
				code: "NOT_FOUND",
				message: "No docker containers could be found, check logs for more information",
			});
		}

		return dockerInfo as unknown as NonNullable<(typeof dockerInfo)[number]>[];
	}),
});

async function getImageData(docker: Docker, container: Docker.ContainerInfo) {
	const inspect = await docker.getImage(container.Image).inspect();
	const digest = inspect.RepoDigests[0];
	const containerInspect = await docker.getContainer(container.Id).inspect();

	let tag = inspect.RepoTags[0]?.split(":")[1];
	tag ??= containerInspect.Config.Image.split(":")[1];

	let name = digest?.split("@")?.[0];
	name ??= container.Image.split(":")[0];

	const hash = digest?.split("@")?.[1]?.split(":")?.[1];

	if (!name) {
		console.error("Container image name could not be found");
		throw new Error("Container image name could not be found");
	}
	if (!hash) {
		console.error("No image hash could be found");
		throw new Error("No image hash could be found");
	}

	return {
		name,
		tag,
		hash,
	};
}

function getDocker() {
	try {
		const docker = new Docker({ socketPath: "/var/run/docker.sock" });
		if (docker) {
			return docker;
		}
		throw new Error("Could not connect to the docker socket");
	} catch (error) {
		console.error("Could not connect to docker socket");
		throw new TRPCError({
			code: "INTERNAL_SERVER_ERROR",
			message: "Could not connect to docker socket",
		});
	}
}

async function getContainers(docker: Docker) {
	try {
		const containers = await docker.listContainers();
		if (containers.length) {
			return containers;
		}
		throw Error("No containers found");
	} catch (error) {
		console.error("Could not get containers from docker socket");
		throw new TRPCError({
			code: "INTERNAL_SERVER_ERROR",
			message: "Could not get containers from docker socket",
		});
	}
}

function isSemver(tag: string): boolean {
	const removeV = tag.replace(/^v/i, "");
	return !!semver.valid(removeV);
}

async function getHash(image: string) {
	const latestImage = await $`bin/regctl image digest ${image}`.text();
	const hash = latestImage.split(":")?.[1]?.substring(0, 12);
	if (!hash) {
		throw new Error("Hash not found", {
			cause: {
				imageDigest: latestImage,
			},
		});
	}
	return hash;
}

async function getLatest(image: string, tag?: string): Promise<{ hash: string; tag?: string }> {
	if (tag && isSemver(tag)) {
		return await getSemverTag(image);
	}

	let imageSh = image;
	if (tag) {
		imageSh += `:${tag}`;
	}

	const hash = await getHash(imageSh);

	return {
		hash,
	};
}

async function getSemverTag(image: string): Promise<{ hash: string; tag?: string }> {
	const allTags = await $`bin/regctl tag ls --exclude 'version.*' --exclude 'unstable.*' --exclude '.*rc.*' --exclude 'lib.*' --exclude 'release.*' --exclude 'arm.*
'  --exclude 'amd.*' ${image}`.text();
	const semverTags = allTags.split("\n").filter((tag) => isSemver(tag));
	const newestTag = semver.rsort(semverTags)[0];

	let imageSh = image;
	if (newestTag) {
		imageSh += `:${newestTag}`;
		return {
			hash: await getHash(imageSh),
			tag: newestTag,
		};
	}
	return {
		hash: await getHash(imageSh),
	};
}

export type dockerRouterType = inferRouterOutputs<typeof dockerRouter>;
