import { z } from "zod";
import { Node } from "@xyflow/react";
import {
	ConfigSchema,
	Domain,
	DomainSchema,
	ServiceType,
	ServiceTypeSchema,
	VolumeType,
	VolumeTypeSchema,
} from "./types.js";

export const serviceAnalyzisSchema = z.object({
	name: z.string(),
	location: z.string(),
	configVars: z.array(
		z.object({
			name: z.string(),
			category: z.enum(["CommandLineFlag", "EnvironmentVariable"]),
			type: z.optional(z.enum(["String", "Number", "Boolean"])),
			semanticType: z.optional(
				z.enum([
					"EXPANDED_ENV_VAR",
					"PORT",
					"FILESYSTEM_PATH",
					"DATABASE_URL",
					"SQLITE_PATH",
					"POSTGRES_URL",
					"POSTGRES_PASSWORD",
					"POSTGRES_USER",
					"POSTGRES_DB",
					"POSTGRES_PORT",
					"POSTGRES_HOST",
					"POSTGRES_SSL",
					"MONGO_URL",
					"MONGO_PASSWORD",
					"MONGO_USER",
					"MONGO_DB",
					"MONGO_PORT",
					"MONGO_HOST",
					"MONGO_SSL",
				]),
			),
		}),
	),
});

export const BoundEnvVarSchema = z.union([
	z.object({
		id: z.string(),
		source: z.null(),
		name: z.string(),
		value: z.string(),
		isEditting: z.boolean().optional(),
	}),
	z.object({
		id: z.string(),
		source: z.string().nullable(),
		portId: z.string(),
		name: z.string(),
		alias: z.string(),
		isEditting: z.boolean(),
	}),
	z.object({
		id: z.string(),
		source: z.string().nullable(),
		name: z.string(),
		alias: z.string(),
		isEditting: z.boolean(),
	}),
	z.object({
		id: z.string(),
		source: z.string().nullable(),
		name: z.string(),
		isEditting: z.boolean(),
	}),
	z.object({
		id: z.string(),
		source: z.string().nullable(),
	}),
]);

export type BoundEnvVar =
	| {
			id: string;
			source: string | null;
	  }
	| {
			id: string;
			source: string | null;
			name: string;
			isEditting: boolean;
	  }
	| {
			id: string;
			source: string | null;
			name: string;
			alias: string;
			isEditting: boolean;
	  }
	| {
			id: string;
			source: string | null;
			portId: string;
			name: string;
			alias: string;
			isEditting: boolean;
	  }
	| {
			id: string;
			source: null;
			name: string;
			value: string;
			isEditting?: boolean;
	  };

export type EnvVar = {
	name: string;
	value: string;
};

export type InitData = {
	label: string;
	envVars: BoundEnvVar[];
	ports: Port[];
};

export type NodeData = InitData & {
	activeField?: string | undefined;
	state?: string | null;
};

export type PortConnectedTo = {
	serviceId: string;
	portId: string;
};

export type NetworkData = NodeData & {
	domain: string;
};

export type NetworkNode = Node<NetworkData> & {
	type: "network";
};

export type GatewayHttpsData = NodeData & {
	readonly?: boolean;
	network?: string;
	subdomain?: string;
	https?: PortConnectedTo;
	auth?: {
		enabled: boolean;
		groups?: string[];
		noAuthPathPatterns?: string[];
	};
};

export type GatewayHttpsNode = Node<GatewayHttpsData> & {
	type: "gateway-https";
};

export type GatewayTCPData = NodeData & {
	readonly?: boolean;
	network?: string;
	subdomain?: string;
	exposed: PortConnectedTo[];
	selected?: {
		serviceId?: string;
		portId?: string;
	};
};

export type GatewayTCPNode = Node<GatewayTCPData> & {
	type: "gateway-tcp";
};

export const PortSchema = z.object({
	id: z.string(),
	name: z.string(),
	value: z.number(),
});

export type Port = z.infer<typeof PortSchema>;

export type ServiceData = NodeData & {
	type: ServiceType;
	repository?:
		| {
				id: number;
				repoNodeId: string;
		  }
		| {
				id: number;
				repoNodeId: string;
				branch: string;
		  }
		| {
				id: number;
				repoNodeId: string;
				branch: string;
				rootDir: string;
		  };
	volume?: string[];
	preBuildCommands?: string;
	isChoosingPortToConnect?: boolean;
	dev?:
		| {
				enabled: false;
				expose?: Domain;
		  }
		| {
				enabled: true;
				mode: "VM";
				expose?: Domain;
				codeServerNodeId: string;
				sshNodeId: string;
		  }
		| {
				enabled: true;
				mode: "PROXY";
				address: string;
		  };
	model?: {
		name: "gemini" | "claude";
		apiKey?: string;
	};
	info?: z.infer<typeof serviceAnalyzisSchema>;
};

export type ServiceNode = Node<ServiceData> & {
	type: "app";
};

export type VolumeData = NodeData & {
	type: VolumeType;
	size: string;
	attachedTo: string[];
};

export type VolumeNode = Node<VolumeData> & {
	type: "volume";
};

export type PostgreSQLData = NodeData & {};

export type PostgreSQLNode = Node<PostgreSQLData> & {
	type: "postgresql";
};

export type MongoDBData = NodeData & {};

export type MongoDBNode = Node<MongoDBData> & {
	type: "mongodb";
};

export type GithubData = NodeData & {
	repository?: {
		id: number;
		sshURL: string;
		fullName: string;
	};
};

export type GithubNode = Node<GithubData> & {
	type: "github";
};

export type NANode = Node<NodeData> & {
	type: undefined;
};

export type AppNode =
	| NetworkNode
	| GatewayHttpsNode
	| GatewayTCPNode
	| ServiceNode
	| VolumeNode
	| PostgreSQLNode
	| MongoDBNode
	| GithubNode
	| NANode;

export type NodeType = Exclude<Pick<AppNode, "type">["type"], undefined>;

export const NetworkSchema = z.object({
	name: z.string().min(1),
	domain: z.string().min(1),
	hasAuth: z.boolean(),
});

export type Network = z.infer<typeof NetworkSchema>;

export const accessSchema = z.discriminatedUnion("type", [
	z.object({
		type: z.literal("https"),
		name: z.string(),
		address: z.string(),
		agentName: z.string().optional(),
	}),
	z.object({
		type: z.literal("ssh"),
		name: z.string(),
		host: z.string(),
		port: z.number(),
	}),
	z.object({
		type: z.literal("tcp"),
		name: z.string(),
		host: z.string(),
		port: z.number(),
	}),
	z.object({
		type: z.literal("udp"),
		name: z.string(),
		host: z.string(),
		port: z.number(),
	}),
	z.object({
		type: z.literal("postgresql"),
		name: z.string(),
		host: z.string(),
		port: z.number(),
		database: z.string(),
		username: z.string(),
		password: z.string(),
	}),
	z.object({
		type: z.literal("mongodb"),
		name: z.string(),
		host: z.string(),
		port: z.number(),
		database: z.string(),
		username: z.string(),
		password: z.string(),
	}),
	z.object({
		type: z.literal("env_var"),
		name: z.string(),
		var: z.string(),
	}),
]);

export const serviceInfoSchema = z.object({
	name: z.string(),
	workers: z.array(
		z.object({
			id: z.string(),
			commit: z
				.object({
					hash: z.string(),
					message: z.string(),
				})
				.nullable()
				.optional(),
			commands: z
				.array(
					z.object({
						command: z.string(),
						state: z.string(),
					}),
				)
				.optional(),
		}),
	),
});

export const envSchema = z.object({
	deployKeyPublic: z.string().optional(),
	instanceId: z.string().optional(),
	networks: z.array(
		z.object({
			name: z.string(),
			domain: z.string(),
			hasAuth: z.boolean(),
		}),
	),
	integrations: z.object({
		github: z.boolean(),
		gemini: z.boolean(),
		anthropic: z.boolean(),
	}),
	services: z.array(serviceInfoSchema),
	user: z.object({
		id: z.string(),
		username: z.string(),
	}),
	access: z.array(accessSchema),
});

export type ServiceInfo = z.infer<typeof serviceInfoSchema>;
export type Env = z.infer<typeof envSchema>;
export type Access = z.infer<typeof accessSchema>;
export type AgentAccess = Required<Extract<Access, { type: "https" }>>;

const NodeBaseSchema = z.object({
	id: z.string(),
	position: z.object({
		x: z.number(),
		y: z.number(),
	}),
});

export const NodeBaseDataSchema = z.object({
	label: z.string(),
	envVars: z.array(BoundEnvVarSchema),
	ports: z.array(PortSchema),
});

export const NetworkNodeSchema = z
	.object({
		type: z.literal("network"),
		data: z
			.object({
				domain: z.string(),
			})
			.extend(NodeBaseDataSchema.shape),
	})
	.extend(NodeBaseSchema.shape);

export const GithubNodeSchema = z
	.object({
		type: z.literal("github"),
		data: z
			.object({
				repository: z
					.object({
						id: z.number(),
						sshURL: z.string(),
						fullName: z.string(),
					})
					.optional(),
			})
			.extend(NodeBaseDataSchema.shape),
	})
	.extend(NodeBaseSchema.shape);

export const GatewayHttpsNodeSchema = z
	.object({
		type: z.literal("gateway-https"),
		data: z
			.object({
				readonly: z.boolean().optional(), // TODO: remove this
				network: z.string().optional(),
				subdomain: z.string().optional(),
				https: z
					.object({
						serviceId: z.string(),
						portId: z.string(),
					})
					.optional(),
				auth: z
					.object({
						enabled: z.boolean(),
						groups: z.array(z.string()).optional(),
						noAuthPathPatterns: z.array(z.string()).optional(),
					})
					.optional(),
			})
			.extend(NodeBaseDataSchema.shape),
	})
	.extend(NodeBaseSchema.shape);

export const GatewayTCPNodeSchema = z
	.object({
		type: z.literal("gateway-tcp"),
		data: z
			.object({
				readonly: z.boolean().optional(),
				network: z.string().optional(),
				subdomain: z.string().optional(),
				exposed: z.array(
					z.object({
						serviceId: z.string(),
						portId: z.string(),
					}),
				),
				selected: z
					.object({
						serviceId: z.string().optional(),
						portId: z.string().optional(),
					})
					.optional(),
			})
			.extend(NodeBaseDataSchema.shape),
	})
	.extend(NodeBaseSchema.shape);

export const ServiceNodeSchema = z
	.object({
		type: z.literal("app"),
		data: z
			.object({
				type: ServiceTypeSchema,
				repository: z
					.union([
						z.object({
							id: z.number(),
							repoNodeId: z.string(),
							branch: z.string(),
							rootDir: z.string(),
						}),
						z.object({
							id: z.number(),
							repoNodeId: z.string(),
							branch: z.string(),
						}),
						z.object({
							id: z.number(),
							repoNodeId: z.string(),
						}),
					])
					.optional(),
				volume: z.array(z.string()).optional(),
				preBuildCommands: z.string().optional(),
				isChoosingPortToConnect: z.boolean().optional(),
				dev: z
					.union([
						z.object({
							enabled: z.literal(false),
							expose: DomainSchema.optional(),
						}),
						z.object({
							enabled: z.literal(true),
							mode: z.literal("VM"),
							expose: DomainSchema.optional(),
							codeServerNodeId: z.string(),
							sshNodeId: z.string(),
						}),
						z.object({
							enabled: z.literal(true),
							mode: z.literal("PROXY"),
							address: z.string(),
						}),
					])
					.optional(),
				model: z
					.object({
						name: z.enum(["gemini", "claude"]),
						apiKey: z.string().optional(),
					})
					.optional(),
				info: serviceAnalyzisSchema.optional(),

				ports: z.array(
					z.object({
						id: z.string(),
						name: z.string(),
						value: z.number(),
					}),
				),
				activeField: z.string().optional(),
				state: z.string().nullable().optional(),
			})
			.extend(NodeBaseDataSchema.shape),
	})
	.extend(NodeBaseSchema.shape);

export const VolumeNodeSchema = z
	.object({
		type: z.literal("volume"),
		data: z
			.object({
				size: z.string(),
				type: VolumeTypeSchema,
				attachedTo: z.array(z.string()),
			})
			.extend(NodeBaseDataSchema.shape),
	})
	.extend(NodeBaseSchema.shape);

export const PostgreSQLNodeSchema = z
	.object({
		type: z.literal("postgresql"),
		data: z.object({}).extend(NodeBaseDataSchema.shape),
	})
	.extend(NodeBaseSchema.shape);

export const MongoDBNodeSchema = z
	.object({
		type: z.literal("mongodb"),
		data: z.object({}).extend(NodeBaseDataSchema.shape),
	})
	.extend(NodeBaseSchema.shape);

export const NodeSchema = z.discriminatedUnion("type", [
	NetworkNodeSchema,
	GithubNodeSchema,
	GatewayHttpsNodeSchema,
	GatewayTCPNodeSchema,
	ServiceNodeSchema,
	VolumeNodeSchema,
	PostgreSQLNodeSchema,
	MongoDBNodeSchema,
]);

export const EdgeSchema = z.object({
	id: z.string(),
	source: z.string(),
	sourceHandle: z.string().optional(),
	target: z.string(),
	targetHandle: z.string().optional(),
});

export const ViewportTransformSchema = z.object({
	transformX: z.number(),
	transformY: z.number(),
	transformZoom: z.number(),
	width: z.number(),
	height: z.number(),
});

export const GraphSchema = z.object({
	nodes: z.array(NodeSchema),
	edges: z.array(EdgeSchema),
	viewport: z
		.object({
			x: z.number(),
			y: z.number(),
			zoom: z.number(),
		})
		.optional(),
	viewportTransform: ViewportTransformSchema.optional(),
});

export const GraphOrConfigSchema = z.discriminatedUnion("type", [
	z.object({
		type: z.literal("graph"),
		graph: GraphSchema,
	}),
	z.object({
		type: z.literal("config"),
		config: ConfigSchema,
	}),
]);

export const GraphConfigOrDraft = z.discriminatedUnion("type", [
	z.object({
		type: z.literal("graph"),
		graph: GraphSchema,
	}),
	z.object({
		type: z.literal("config"),
		config: ConfigSchema,
	}),
	z.object({
		type: z.literal("draft"),
	}),
]);

export type Edge = z.infer<typeof EdgeSchema>;
export type ViewportTransform = z.infer<typeof ViewportTransformSchema>;
export type Graph = z.infer<typeof GraphSchema>;
