import { readFileSync } from "node:fs";
import { prettifyError, z } from "zod";
import "dotenv/config";
import { resolveResticHostname } from "../../../apps/agent/src/restic/hostname";
import { buildAllowedHosts } from "../lib/auth/base-url";
import { toMessage } from "@zerobyte/core/utils";

const unquote = (str: string) => str.trim().replace(/^(['"])(.*)\1$/, "$2");

const envSchema = z
	.object({
		NODE_ENV: z.enum(["development", "production", "test"]).default("production"),
		SERVER_IP: z.string().default("localhost"),
		SERVER_IDLE_TIMEOUT: z.coerce.number().int().default(60),
		WEBHOOK_TIMEOUT: z.coerce.number().int().default(60),
		RESTIC_HOSTNAME: z.string().optional(),
		PORT: z.coerce.number().int().default(4096),
		MIGRATIONS_PATH: z.string().optional(),
		APP_VERSION: z.string().default("dev"),
		TRUSTED_ORIGINS: z.string().optional(),
		PORTLESS_URL: z.string().optional(),
		PORTLESS_TAILSCALE_URL: z.string().optional(),
		TRUST_PROXY: z.string().default("false"),
		DISABLE_RATE_LIMITING: z.string().default("false"),
		APP_SECRET: z.preprocess((value) => (value === "" ? undefined : value), z.string().min(32).max(256).optional()),
		APP_SECRET_FILE: z.string().optional(),
		BASE_URL: z.string(),
		ENABLE_DEV_PANEL: z.string().default("false"),
		ENABLE_LOCAL_AGENT: z.string().default("false"),
		WEBHOOK_ALLOWED_ORIGINS: z.string().optional(),
		PROVISIONING_PATH: z.string().optional(),
	})
	.transform((s, ctx) => {
		let baseUrl = unquote(s.BASE_URL);
		const trustedOrigins = s.TRUSTED_ORIGINS?.split(",").map(unquote).filter(Boolean) ?? [];

		if (s.NODE_ENV === "development") {
			if (s.PORTLESS_URL) {
				trustedOrigins.push(unquote(s.PORTLESS_URL));
			}

			if (s.PORTLESS_TAILSCALE_URL) {
				baseUrl = unquote(s.PORTLESS_TAILSCALE_URL);
				trustedOrigins.push(baseUrl);
			}
		}

		trustedOrigins.push(baseUrl);
		const uniqueTrustedOrigins = Array.from(new Set(trustedOrigins));
		const webhookAllowedOrigins = s.WEBHOOK_ALLOWED_ORIGINS?.split(",").map(unquote).filter(Boolean) ?? [];
		const authOrigins = [baseUrl, ...uniqueTrustedOrigins];
		const { allowedHosts, invalidOrigins } = buildAllowedHosts(authOrigins);
		let appSecret = s.APP_SECRET;

		if (!appSecret && !s.APP_SECRET_FILE) {
			const errorMessage = [
				"",
				"================================================================================",
				"APP_SECRET is not configured.",
				"",
				"This secret is required for encrypting sensitive data in the database.",
				"",
				"To generate a new secret, run:",
				"  openssl rand -hex 32",
				"",
				"Then set the APP_SECRET environment variable with the generated value.",
				"",
				"IMPORTANT: Store this secret securely and back it up. If lost, encrypted data",
				"in the database will be unrecoverable.",
				"================================================================================",
			].join("\n");

			ctx.addIssue({
				code: "custom",
				message: errorMessage,
			});
		}

		if (s.APP_SECRET && s.APP_SECRET_FILE) {
			ctx.addIssue({
				code: "custom",
				message: "Both APP_SECRET and APP_SECRET_FILE are set. Please set only one of these.",
			});
		}

		if (s.APP_SECRET_FILE) {
			try {
				appSecret = readFileSync(s.APP_SECRET_FILE, "utf-8").trim();
				if (appSecret.length < 32 || appSecret.length > 256) {
					ctx.addIssue({
						code: "custom",
						message: "The secret read from APP_SECRET_FILE must be between 32 and 256 characters long.",
					});
				}
			} catch (err) {
				ctx.addIssue({
					code: "custom",
					message: `Failed to read APP_SECRET from file: ${toMessage(err)}`,
				});
			}
		}

		for (const origin of invalidOrigins) {
			console.warn(
				`Ignoring invalid origin in configuration: ${origin}. Make sure it is a valid URL with a protocol (e.g. https://example.com)`,
			);
		}

		if (allowedHosts.length === 0) {
			ctx.addIssue({
				code: "custom",
				message:
					"No valid trusted origins provided. Please check the BASE_URL and TRUSTED_ORIGINS environment variables.",
			});
		}

		return {
			__prod__: s.NODE_ENV === "production",
			environment: s.NODE_ENV,
			serverIp: s.SERVER_IP,
			serverIdleTimeout: s.SERVER_IDLE_TIMEOUT,
			webhookTimeout: s.WEBHOOK_TIMEOUT,
			resticHostname: s.RESTIC_HOSTNAME || resolveResticHostname(),
			port: s.PORT,
			migrationsPath: s.MIGRATIONS_PATH,
			appVersion: s.APP_VERSION,
			trustedOrigins: uniqueTrustedOrigins,
			trustProxy: s.TRUST_PROXY === "true",
			appSecret: appSecret ?? "",
			baseUrl,
			isSecure: baseUrl.startsWith("https://"),
			flags: {
				disableRateLimiting: s.DISABLE_RATE_LIMITING === "true" || s.NODE_ENV === "test",
				enableDevPanel: s.ENABLE_DEV_PANEL === "true",
				enableLocalAgent: s.ENABLE_LOCAL_AGENT === "true",
			},
			provisioningPath: s.PROVISIONING_PATH,
			allowedHosts,
			webhookAllowedOrigins,
		};
	});

export const parseConfig = (env: unknown) => {
	const result = envSchema.safeParse(env);

	if (!result.success) {
		console.error(`Environment variable validation failed: ${prettifyError(result.error)}`);
		process.exit(1);
	}

	if (!result.data.appSecret) {
		console.error(
			"APP_SECRET is required but was not provided. Please set the APP_SECRET environment variable or provide a file with APP_SECRET_FILE.",
		);
		process.exit(1);
	}

	return result.data;
};

export const config = parseConfig(process.env);
