1use crate::platform::api::client::PlatformApiClient;
7use crate::platform::api::types::{ClusterSummary, Environment};
8use crate::wizard::provider_selection::get_provider_deployment_statuses;
9use crate::wizard::render::{display_step_header, wizard_render_config};
10use colored::Colorize;
11use inquire::{InquireError, Select, Text};
12
13#[derive(Debug, Clone, PartialEq, Eq)]
17enum EnvironmentType {
18 Cluster,
19 Cloud,
20}
21
22impl EnvironmentType {
23 fn as_str(&self) -> &'static str {
24 match self {
25 EnvironmentType::Cluster => "cluster",
26 EnvironmentType::Cloud => "cloud",
27 }
28 }
29
30 fn display_name(&self) -> &'static str {
31 match self {
32 EnvironmentType::Cluster => "Kubernetes",
33 EnvironmentType::Cloud => "Cloud Runner",
34 }
35 }
36}
37
38#[derive(Debug)]
40pub enum EnvironmentCreationResult {
41 Created(Environment),
43 Cancelled,
45 Error(String),
47}
48
49pub async fn create_environment_wizard(
56 client: &PlatformApiClient,
57 project_id: &str,
58) -> EnvironmentCreationResult {
59 display_step_header(
60 1,
61 "Create Environment",
62 "Set up a new deployment environment for your project.",
63 );
64
65 let name = match Text::new("Environment name:")
67 .with_placeholder("e.g., production, staging, development")
68 .with_help_message("Choose a descriptive name for this environment")
69 .prompt()
70 {
71 Ok(name) => {
72 if name.trim().is_empty() {
73 println!("\n{}", "Environment name cannot be empty.".red());
74 return EnvironmentCreationResult::Cancelled;
75 }
76 name.trim().to_string()
77 }
78 Err(InquireError::OperationCanceled) | Err(InquireError::OperationInterrupted) => {
79 println!("\n{}", "Wizard cancelled.".dimmed());
80 return EnvironmentCreationResult::Cancelled;
81 }
82 Err(e) => {
83 return EnvironmentCreationResult::Error(format!("Input error: {}", e));
84 }
85 };
86
87 display_step_header(
89 2,
90 "Select Target Type",
91 "Choose how this environment will deploy services.",
92 );
93
94 let target_options = vec![
95 format!(
96 "{} {}",
97 "Cloud Runner".cyan(),
98 "Fully managed, auto-scaling containers".dimmed()
99 ),
100 format!(
101 "{} {}",
102 "Kubernetes".cyan(),
103 "Deploy to your own K8s cluster".dimmed()
104 ),
105 ];
106
107 let target_selection = Select::new("Select target type:", target_options)
108 .with_render_config(wizard_render_config())
109 .with_help_message("Cloud Runner: serverless, Kubernetes: full control")
110 .prompt();
111
112 let env_type = match target_selection {
113 Ok(answer) => {
114 if answer.contains("Cloud Runner") {
115 EnvironmentType::Cloud
116 } else {
117 EnvironmentType::Cluster
118 }
119 }
120 Err(InquireError::OperationCanceled) | Err(InquireError::OperationInterrupted) => {
121 println!("\n{}", "Wizard cancelled.".dimmed());
122 return EnvironmentCreationResult::Cancelled;
123 }
124 Err(e) => {
125 return EnvironmentCreationResult::Error(format!("Selection error: {}", e));
126 }
127 };
128
129 println!(
130 "\n{} Target: {}",
131 "✓".green(),
132 env_type.display_name().bold()
133 );
134
135 let cluster_id = if env_type == EnvironmentType::Cluster {
137 match select_cluster_for_env(client, project_id).await {
138 ClusterSelectionResult::Selected(id) => Some(id),
139 ClusterSelectionResult::NoClusters => {
140 println!(
141 "\n{}",
142 "No Kubernetes clusters available. Please provision a cluster first.".red()
143 );
144 return EnvironmentCreationResult::Cancelled;
145 }
146 ClusterSelectionResult::Cancelled => {
147 return EnvironmentCreationResult::Cancelled;
148 }
149 ClusterSelectionResult::Error(e) => {
150 return EnvironmentCreationResult::Error(e);
151 }
152 }
153 } else {
154 None
155 };
156
157 println!("\n{}", "Creating environment...".dimmed());
159
160 match client
161 .create_environment(
162 project_id,
163 &name,
164 env_type.as_str(),
165 cluster_id.as_deref(),
166 )
167 .await
168 {
169 Ok(env) => {
170 println!(
171 "\n{} Environment {} created successfully!",
172 "✓".green().bold(),
173 env.name.bold()
174 );
175 println!(" ID: {}", env.id.dimmed());
176 println!(" Type: {}", env.environment_type);
177 if let Some(cid) = &env.cluster_id {
178 println!(" Cluster: {}", cid);
179 }
180 EnvironmentCreationResult::Created(env)
181 }
182 Err(e) => EnvironmentCreationResult::Error(format!("Failed to create environment: {}", e)),
183 }
184}
185
186enum ClusterSelectionResult {
188 Selected(String),
189 NoClusters,
190 Cancelled,
191 Error(String),
192}
193
194async fn select_cluster_for_env(
196 client: &PlatformApiClient,
197 project_id: &str,
198) -> ClusterSelectionResult {
199 display_step_header(
200 3,
201 "Select Cluster",
202 "Choose a Kubernetes cluster for this environment.",
203 );
204
205 let clusters: Vec<ClusterSummary> =
207 match get_available_clusters_for_project(client, project_id).await {
208 Ok(c) => c,
209 Err(e) => return ClusterSelectionResult::Error(e),
210 };
211
212 if clusters.is_empty() {
213 return ClusterSelectionResult::NoClusters;
214 }
215
216 let options: Vec<String> = clusters
218 .iter()
219 .map(|c| {
220 let health = if c.is_healthy {
221 "healthy".green()
222 } else {
223 "unhealthy".red()
224 };
225 format!("{} ({}) - {}", c.name.bold(), c.region.dimmed(), health)
226 })
227 .collect();
228
229 let selection = Select::new("Select cluster:", options.clone())
230 .with_render_config(wizard_render_config())
231 .with_help_message("Choose the cluster to deploy to")
232 .prompt();
233
234 match selection {
235 Ok(answer) => {
236 let selected_name = answer.split(" (").next().unwrap_or("");
238 if let Some(cluster) = clusters.iter().find(|c| c.name == selected_name) {
239 println!("\n{} Selected: {}", "✓".green(), cluster.name.bold());
240 ClusterSelectionResult::Selected(cluster.id.clone())
241 } else {
242 ClusterSelectionResult::Error("Failed to match selected cluster".to_string())
243 }
244 }
245 Err(InquireError::OperationCanceled) | Err(InquireError::OperationInterrupted) => {
246 println!("\n{}", "Wizard cancelled.".dimmed());
247 ClusterSelectionResult::Cancelled
248 }
249 Err(e) => ClusterSelectionResult::Error(format!("Selection error: {}", e)),
250 }
251}
252
253async fn get_available_clusters_for_project(
255 client: &PlatformApiClient,
256 project_id: &str,
257) -> Result<Vec<ClusterSummary>, String> {
258 let statuses = get_provider_deployment_statuses(client, project_id)
260 .await
261 .map_err(|e| format!("Failed to get provider statuses: {}", e))?;
262
263 let mut all_clusters = Vec::new();
265 for status in statuses {
266 if status.is_connected {
267 all_clusters.extend(status.clusters);
268 }
269 }
270
271 Ok(all_clusters)
272}
273
274#[cfg(test)]
275mod tests {
276 use super::*;
277
278 #[test]
279 fn test_environment_creation_result_variants() {
280 let created = EnvironmentCreationResult::Created(Environment {
281 id: "env-1".to_string(),
282 name: "test".to_string(),
283 project_id: "proj-1".to_string(),
284 environment_type: "cloud".to_string(),
285 cluster_id: None,
286 namespace: None,
287 description: None,
288 is_active: true,
289 created_at: None,
290 updated_at: None,
291 });
292 assert!(matches!(created, EnvironmentCreationResult::Created(_)));
293
294 let cancelled = EnvironmentCreationResult::Cancelled;
295 assert!(matches!(cancelled, EnvironmentCreationResult::Cancelled));
296
297 let error = EnvironmentCreationResult::Error("test error".to_string());
298 assert!(matches!(error, EnvironmentCreationResult::Error(_)));
299 }
300
301 #[test]
302 fn test_environment_type_as_str() {
303 assert_eq!(EnvironmentType::Cluster.as_str(), "cluster");
304 assert_eq!(EnvironmentType::Cloud.as_str(), "cloud");
305 }
306
307 #[test]
308 fn test_environment_type_display_name() {
309 assert_eq!(EnvironmentType::Cluster.display_name(), "Kubernetes");
310 assert_eq!(EnvironmentType::Cloud.display_name(), "Cloud Runner");
311 }
312}