import { z } from "zod";

const AuthDisabledSchema = z.object({
	enabled: z.literal(false),
	groups: z.undefined(),
	noAuthPathPatterns: z.undefined(),
});

const AuthEnabledSchema = z.object({
	enabled: z.literal(true),
	groups: z.string().optional(),
	noAuthPathPatterns: z.array(z.string()),
});

const AuthSchema = z.union([AuthDisabledSchema, AuthEnabledSchema]);

const IngressSchema = z.object({
	nodeId: z.string().optional(),
	network: z.string(),
	subdomain: z.string(),
	port: z.union([z.object({ name: z.string() }), z.object({ value: z.string() })]),
	auth: AuthSchema,
});

export const DomainSchema = z.object({
	nodeId: z.string().optional(),
	network: z.string(),
	subdomain: z.string(),
});

const PortValueSchema = z.union([
	z.object({
		name: z.string(),
	}),
	z.object({
		value: z.number(),
	}),
]);

const PortDomainSchema = DomainSchema.extend({
	port: PortValueSchema,
});

export const ServiceTypes = [
	"deno:2.2.0",
	"golang:1.20.0",
	"golang:1.22.0",
	"golang:1.24.0",
	"hugo:latest",
	"php:8.2-apache",
	"nextjs:deno-2.0.0",
	"nodejs:23.1.0",
	"nodejs:24.0.2",
	"sketch:latest",
] as const;

export const ServiceTypeSchema = z.enum(ServiceTypes);

const ModelSchema = z.discriminatedUnion("name", [
	z.object({
		name: z.literal("claude"),
		anthropicApiKey: z.string().optional(),
	}),
	z.object({
		name: z.literal("gemini"),
		geminiApiKey: z.string().optional(),
	}),
]);

const ServiceSchema = z.object({
	nodeId: z.string().optional(),
	type: ServiceTypeSchema,
	name: z.string(),
	source: z
		.object({
			repository: z.string(),
			branch: z.string(),
			rootDir: z.string(),
		})
		.optional(),
	ports: z
		.array(
			z.object({
				name: z.string(),
				value: z.number(),
				protocol: z.enum(["TCP", "UDP"]),
			}),
		)
		.optional(),
	env: z
		.array(
			z.object({
				name: z.string(),
				alias: z.string().optional(),
				value: z.string().optional(),
			}),
		)
		.optional(),
	ingress: z.array(IngressSchema).optional(),
	expose: z.array(PortDomainSchema).optional(),
	volume: z.array(z.string()).optional(),
	preBuildCommands: z.array(z.object({ bin: z.string() })).optional(),
	dev: z.union([
		z.object({ enabled: z.literal(false) }),
		z.object({
			enabled: z.literal(true),
			mode: z.literal("VM"),
			username: z.string().optional(),
			ssh: DomainSchema.optional(),
			codeServer: DomainSchema.optional(),
		}),
		z.object({
			enabled: z.literal(true),
			mode: z.literal("PROXY"),
			address: z.string(),
			vpn: z
				.discriminatedUnion("enabled", [
					z.object({ enabled: z.literal(false) }),
					z.object({ enabled: z.literal(true), username: z.string() }),
				])
				.optional(),
		}),
	]),
	model: ModelSchema.optional(),
});

const AgentSchema = ServiceSchema.extend({
	type: z.literal("sketch:latest"),
	model: ModelSchema,
});

export const VolumeTypeSchema = z.enum(["ReadWriteOnce", "ReadOnlyMany", "ReadWriteMany", "ReadWriteOncePod"]);

const VolumeSchema = z.object({
	nodeId: z.string().optional(),
	name: z.string(),
	size: z.string(),
	accessMode: VolumeTypeSchema,
});

const PostgreSQLSchema = z.object({
	nodeId: z.string().optional(),
	name: z.string(),
	size: z.string(),
	expose: z.array(DomainSchema).optional(),
});

const MongoDBSchema = z.object({
	nodeId: z.string().optional(),
	name: z.string(),
	size: z.string(),
	expose: z.array(DomainSchema).optional(),
});

export const MachineSchema = z.object({
	name: z.string(),
	given_name: z.string(),
	last_seen: z
		.object({
			seconds: z.number(),
			nanos: z.number(),
		})
		.optional(),
});

export const MachinesSchema = z.array(MachineSchema);

export const ConfigSchema = z.object({
	service: z.array(ServiceSchema).optional(),
	agent: z.array(AgentSchema).optional(),
	volume: z.array(VolumeSchema).optional(),
	postgresql: z.array(PostgreSQLSchema).optional(),
	mongodb: z.array(MongoDBSchema).optional(),
});

export const InputSchema = z.object({
	appId: z.string(),
	managerAddr: z.string(),
	key: z
		.object({
			public: z.string(),
			private: z.string(),
		})
		.optional(),
	geminiApiKey: z.string().optional(),
	anthropicApiKey: z.string().optional(),
});

export const ConfigWithInputSchema = ConfigSchema.extend({
	input: InputSchema,
});

export type AuthDisabled = z.infer<typeof AuthDisabledSchema>;
export type AuthEnabled = z.infer<typeof AuthEnabledSchema>;
export type Auth = z.infer<typeof AuthSchema>;
export type Ingress = z.infer<typeof IngressSchema>;
export type Domain = z.infer<typeof DomainSchema>;
export type PortValue = z.infer<typeof PortValueSchema>;
export type PortDomain = z.infer<typeof PortDomainSchema>;
export type ServiceType = z.infer<typeof ServiceTypeSchema>;
export type Agent = z.infer<typeof AgentSchema>;
export type Service = z.infer<typeof ServiceSchema>;
export type VolumeType = z.infer<typeof VolumeTypeSchema>;
export type Volume = z.infer<typeof VolumeSchema>;
export type PostgreSQL = z.infer<typeof PostgreSQLSchema>;
export type MongoDB = z.infer<typeof MongoDBSchema>;
export type Config = z.infer<typeof ConfigSchema>;
export type ConfigWithInput = z.infer<typeof ConfigWithInputSchema>;
export type Machine = z.infer<typeof MachineSchema>;
export type Machines = z.infer<typeof MachinesSchema>;

// Dev configuration types
export type DevDisabled = { enabled: false };
export type DevVM = {
	enabled: true;
	mode: "VM";
	username?: string;
	ssh?: Domain;
	codeServer?: Domain;
};
export type DevProxy = {
	enabled: true;
	mode: "PROXY";
	address: string;
};
export type Dev = DevDisabled | DevVM | DevProxy;

export const isAgent = (s: Service): s is Agent => s.type === "sketch:latest";
