File size: 28,279 Bytes
e9d5b7d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602

"use server";

import type { DeploymentFormInput } from "@/lib/schemas";
import type { Deployment, DeploymentStatus } from "@/lib/types";
import { analyzeDeploymentLogs as analyzeLogsFlow, type AnalyzeDeploymentLogsInput } from "@/ai/flows/analyze-deployment-logs";
import { getDb } from "@/lib/mongodb";
import { ObjectId } from 'mongodb';
import { getLoggedInUser } from "./auth";
import { getPlatformApiKey } from "./admin";
import { revalidatePath } from "next/cache";

const defaultGithubRepoUrl = "https://github.com/DavidCyrilTech/Anita-V4";
const DEPLOYMENT_COST = 10;
// GLOBAL_DEPLOYMENT_LIMIT removed
const HEROKU_API_BASE_URL = "https://api.heroku.com";
const HEROKU_STACK = "heroku-22";

async function getHerokuApiKey(): Promise<string | null> {
  const result = await getPlatformApiKey();
  if (result.success && result.apiKey && result.apiKey.trim() !== "") {
    return result.apiKey;
  }
  console.warn("Heroku API Key not found or is empty in database settings via Admin Panel. Deployments will fail.");
  return null;
}

async function herokuApiCall(
  endpoint: string,
  method: 'GET' | 'POST' | 'PATCH' | 'DELETE',
  apiKey: string,
  body?: any
): Promise<any> {
  const headers = {
    'Accept': 'application/vnd.heroku+json; version=3',
    'Authorization': `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  };

  const options: RequestInit = {
    method,
    headers,
  };

  if (body) {
    options.body = JSON.stringify(body);
  }

  try {
    const response = await fetch(`${HEROKU_API_BASE_URL}${endpoint}`, options);
    if (!response.ok) {
      if (method === 'DELETE' && response.status === 404) {
        return { status: 404, message: "Resource not found on Heroku (already deleted or never existed)." };
      }
      const errorBody = await response.json().catch(() => ({ message: response.statusText }));
      console.error(`Heroku API Error (${method} ${endpoint}): ${response.status}`, errorBody);
      throw new Error(`Heroku API Error: ${errorBody.message || response.statusText} (Status: ${response.status})`);
    }
    if (response.status === 204 || response.headers.get("content-length") === "0") {
        return null;
    }
    return response.json();
  } catch (error) {
    console.error(`Network or parsing error during Heroku API call (${method} ${endpoint}):`, error);
    throw error;
  }
}


export async function createNewDeployment(data: DeploymentFormInput): Promise<{ success: boolean; message: string; deployment?: Deployment }> {
  const user = await getLoggedInUser();
  if (!user) {
    return { success: false, message: "You must be logged in to create a deployment." };
  }

  const herokuApiKey = await getHerokuApiKey();
  if (!herokuApiKey) {
    return { success: false, message: "Platform API Key is not configured. Deployment cannot proceed." };
  }

  try {
    const db = await getDb();
    const deploymentsCollection = db.collection<Deployment>("deployments");
    const usersCollection = db.collection("users");

    // Removed GLOBAL_DEPLOYMENT_LIMIT check here

    if (user.role !== 'admin') {
      const freshUser = await usersCollection.findOne({ _id: user._id });
      if (!freshUser || freshUser.coins < DEPLOYMENT_COST) {
        return { success: false, message: `Insufficient coins. You need ${DEPLOYMENT_COST} coins. You have ${freshUser?.coins || 0}.` };
      }
    }

    const appNameInput = data.PLATFORM_APP_NAME || `anita-bot-${user.name.toLowerCase().replace(/[^a-z0-9]/g, '').slice(0,8)}-${new ObjectId().toString().slice(-4)}`;
    const herokuAppName = appNameInput.toLowerCase().replace(/[^a-z0-9-]/g, '').slice(0, 30);
    const appNameProvidedByUser = !!data.PLATFORM_APP_NAME;

    const existingDeployment = await deploymentsCollection.findOne({ appName: herokuAppName });
    if (existingDeployment) {
      return { success: false, message: `A deployment with app name '${herokuAppName}' already exists in our records. Please choose a different name or let one be auto-generated.` };
    }

    const now = new Date();
    const initialLogs: string[] = [
      `[SYSTEM] Info: ${now.toISOString()} - Deployment creation initiated by user ${user.email} for Heroku app: ${herokuAppName}.`,
      `[SYSTEM] Info: ${now.toISOString()} - Target GitHub Repo: ${defaultGithubRepoUrl}`
    ];

    let herokuApp;
    try {
      initialLogs.push(`[SYSTEM] Info: ${now.toISOString()} - Attempting to create Heroku app '${herokuAppName}'...`);
      herokuApp = await herokuApiCall('/apps', 'POST', herokuApiKey, { name: herokuAppName, region: 'us', stack: HEROKU_STACK });
      initialLogs.push(`[SYSTEM] Success: ${new Date().toISOString()} - Heroku app '${herokuAppName}' (ID: ${herokuApp.id}) created successfully. Web URL: ${herokuApp.web_url}`);
    } catch (error: any) {
      if (error.message && error.message.includes("Name") && error.message.includes("is already taken") && error.message.includes("(Status: 422)")) {
        initialLogs.push(`[SYSTEM] Error: ${new Date().toISOString()} - Heroku app name '${herokuAppName}' is already taken.`);
        let userMessage = `The app name '${herokuAppName}' is already taken on Heroku. `;
        if (appNameProvidedByUser) {
            userMessage += "Please choose a different name for your app and try again.";
        } else {
            userMessage += "This name was auto-generated. Please try deploying again, as a new name will be generated.";
        }
        return { success: false, message: userMessage };
      }

      initialLogs.push(`[SYSTEM] Error: ${new Date().toISOString()} - Failed to create Heroku app: ${error.message}`);
      const tempFailDeployment: Omit<Deployment, '_id'> = {
        id: herokuAppName, 
        userId: user._id,
        appName: herokuAppName,
        status: 'failed',
        createdAt: now.toISOString(),
        region: 'us',
        logs: initialLogs,
        envVariables: { ...data },
        githubRepoUrl: defaultGithubRepoUrl,
      };
      await deploymentsCollection.insertOne(tempFailDeployment);
      return { success: false, message: `Failed to create Heroku app: ${error.message}` };
    }

    initialLogs.push(`[SYSTEM] Info: ${new Date().toISOString()} - Setting environment variables for '${herokuAppName}'...`);
    const envVarsToSet = { ...data };
    delete envVarsToSet.PLATFORM_APP_NAME;
    const herokuConfigVars = { ...envVarsToSet, GITHUB_REPO_URL: defaultGithubRepoUrl };

    try {
      await herokuApiCall(`/apps/${herokuApp.id}/config-vars`, 'PATCH', herokuApiKey, herokuConfigVars);
      initialLogs.push(`[SYSTEM] Success: ${new Date().toISOString()} - Environment variables set successfully.`);
    } catch (error: any) {
      initialLogs.push(`[SYSTEM] Error: ${new Date().toISOString()} - Failed to set environment variables: ${error.message}. Cleaning up Heroku app.`);
      await herokuApiCall(`/apps/${herokuApp.id}`, 'DELETE', herokuApiKey).catch(delErr => console.error("Cleanup error after failing to set config vars:", delErr));
      return { success: false, message: `Failed to set Heroku environment variables: ${error.message}` };
    }

    initialLogs.push(`[SYSTEM] Info: ${new Date().toISOString()} - Triggering build from GitHub repository '${defaultGithubRepoUrl}'...`);
    let build;
    try {
      build = await herokuApiCall(`/apps/${herokuApp.id}/builds`, 'POST', herokuApiKey, {
        source_blob: {
          url: `${defaultGithubRepoUrl}/tarball/main/`,
          version: 'main'
        },
      });
      initialLogs.push(`[SYSTEM] Success: ${new Date().toISOString()} - Build initiated successfully. Build ID: ${build.id}, Status: ${build.status}.`);
    } catch (error: any) {
      initialLogs.push(`[SYSTEM] Error: ${new Date().toISOString()} - Failed to trigger build: ${error.message}. Cleaning up Heroku app.`);
      await herokuApiCall(`/apps/${herokuApp.id}`, 'DELETE', herokuApiKey).catch(delErr => console.error("Cleanup error after failing to trigger build:", delErr));
      return { success: false, message: `Failed to trigger Heroku build: ${error.message}` };
    }

    const newDeploymentData: Omit<Deployment, '_id'> = {
      id: herokuApp.id,
      userId: user._id,
      appName: herokuApp.name,
      status: build.status === 'succeeded' ? 'succeeded' : 'deploying',
      createdAt: now.toISOString(),
      lastDeployedAt: build.status === 'succeeded' ? new Date(build.updated_at).toISOString() : undefined,
      region: herokuApp.region.name,
      url: herokuApp.web_url,
      logs: initialLogs,
      envVariables: { ...data },
      githubRepoUrl: defaultGithubRepoUrl,
    };

    const result = await deploymentsCollection.insertOne(newDeploymentData);
    if (!result.insertedId) {
      initialLogs.push(`[SYSTEM] Error: ${new Date().toISOString()} - Failed to save deployment to local database. Critical error. Heroku app '${herokuApp.name}' might be orphaned.`);
      await herokuApiCall(`/apps/${herokuApp.id}`, 'DELETE', herokuApiKey).catch(delErr => console.error("Critical: Cleanup error for orphaned Heroku app:", delErr));
      return { success: false, message: "Critical: Failed to save deployment to local database after Heroku setup." };
    }

    if (user.role !== 'admin') {
      await usersCollection.updateOne(
        { _id: user._id },
        { $inc: { coins: -DEPLOYMENT_COST } }
      );
    }

    revalidatePath("/dashboard");
    revalidatePath(`/dashboard/deployments/${newDeploymentData.id}`);

    return {
      success: true,
      message: `Heroku deployment for '${newDeploymentData.appName}' initiated! ${user.role !== 'admin' ? `${DEPLOYMENT_COST} coins deducted.` : ''} Build status: ${build.status}. Check deployment details for updates.`,
      deployment: { ...newDeploymentData, _id: result.insertedId.toString() }
    };

  } catch (error: any) {
    console.error("Error creating new Heroku deployment:", error);
    const message = error.message || "An unexpected error occurred during Heroku deployment.";
    return { success: false, message };
  }
}

export async function getDeployments(): Promise<Deployment[]> {
  const user = await getLoggedInUser();
  if (!user) return [];

  try {
    const db = await getDb();
    const query = user.role === 'admin' ? {} : { userId: user._id };
    const deployments = await db.collection<Deployment>("deployments")
      .find(query)
      .sort({ createdAt: -1 })
      .toArray();

    return deployments.map(d => ({ ...d, _id: d._id?.toString() }));
  } catch (error) {
    console.error("Error fetching deployments:", error);
    return [];
  }
}

export async function getDeploymentById(id: string): Promise<Deployment | null> {
  const user = await getLoggedInUser();
  if (!user) return null;

  try {
    const db = await getDb();
    const deployment = await db.collection<Deployment>("deployments").findOne({ id: id });

    if (!deployment) return null;
    if (deployment.userId !== user._id && user.role !== 'admin') return null;

    if (deployment.status === 'deploying' || deployment.status === 'pending') {
      const herokuApiKey = await getHerokuApiKey();
      if (herokuApiKey) {
        try {
          const formation = await herokuApiCall(`/apps/${deployment.id}/formation/web`, 'GET', herokuApiKey);
          let appStatusBasedOnDynos: DeploymentStatus = deployment.status;
          if (formation && formation.quantity > 0) {
             if(deployment.status === 'deploying') appStatusBasedOnDynos = 'succeeded';
          } else if (formation && formation.quantity === 0 && deployment.status !== 'stopped') {
            appStatusBasedOnDynos = 'stopped';
          }

          const builds = await herokuApiCall(`/apps/${deployment.id}/builds`, 'GET', herokuApiKey);
          let newStatusFromBuild = deployment.status;
          let lastDeployedAtFromBuild = deployment.lastDeployedAt;

          if (builds && builds.length > 0) {
            const latestBuild = builds.sort((a:any, b:any) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime())[0];
            newStatusFromBuild = latestBuild.status === 'succeeded' ? 'succeeded' : latestBuild.status === 'failed' ? 'failed' : 'deploying';
            lastDeployedAtFromBuild = new Date(latestBuild.updated_at).toISOString();
          }

          let finalNewStatus = deployment.status;
          if (newStatusFromBuild === 'succeeded' || newStatusFromBuild === 'failed') {
            finalNewStatus = newStatusFromBuild;
          } else if (appStatusBasedOnDynos !== deployment.status) {
            finalNewStatus = appStatusBasedOnDynos;
          }

          if (finalNewStatus !== deployment.status || (finalNewStatus === 'succeeded' && lastDeployedAtFromBuild !== deployment.lastDeployedAt)) {
            deployment.status = finalNewStatus;
            deployment.lastDeployedAt = finalNewStatus === 'succeeded' ? lastDeployedAtFromBuild : deployment.lastDeployedAt;

            const statusUpdateLog = `[SYSTEM] Info: ${new Date().toISOString()} - Heroku status sync. App status: ${finalNewStatus}. Latest build status: ${newStatusFromBuild}.`;
            const updatedLogs = [...(deployment.logs || []), statusUpdateLog].slice(-500);

            await db.collection<Deployment>("deployments").updateOne(
              { id: deployment.id },
              { $set: { status: finalNewStatus, lastDeployedAt: deployment.lastDeployedAt, logs: updatedLogs } }
            );
            deployment.logs = updatedLogs;
            revalidatePath(`/dashboard/deployments/${deployment.id}`);
          }
        } catch (herokuError: any) {
          const errorLog = `[SYSTEM] Warning: ${new Date().toISOString()} - Could not fetch live status from Heroku: ${herokuError.message}`;
           const updatedLogs = [...(deployment.logs || []), errorLog].slice(-500);
          await db.collection<Deployment>("deployments").updateOne(
             { id: deployment.id },
             { $set: { logs: updatedLogs } }
           );
           deployment.logs = updatedLogs;
        }
      }
    }
    return { ...deployment, _id: deployment._id?.toString() };
  } catch (error) {
    console.error(`Error fetching deployment by ID '${id}':`, error);
    return null;
  }
}


export async function getDeploymentLogs(deploymentId: string): Promise<string[]> {
  const user = await getLoggedInUser();
  if (!user) return [`[SYSTEM] Error: Unauthorized to fetch logs.`];

  const herokuApiKey = await getHerokuApiKey();
  if (!herokuApiKey) return [`[SYSTEM] Error: Heroku API Key not configured. Cannot fetch live logs.`];

  const db = await getDb();
  const deploymentsCollection = db.collection<Deployment>("deployments");
  const deployment = await deploymentsCollection.findOne({ id: deploymentId });

  if (!deployment) return [`[SYSTEM] Info: No deployment found with ID ${deploymentId}.`];
  if (deployment.userId !== user._id && user.role !== 'admin') {
    return [`[SYSTEM] Error: Unauthorized to fetch logs for this deployment.`];
  }

  const timestamp = new Date().toISOString();
  let fetchedHerokuLogs: string[] = [];

  try {
    const logSession = await herokuApiCall(
      `/apps/${deployment.id}/log-sessions`,
      'POST',
      herokuApiKey,
      { lines: 200, source: 'app', tail: false } 
    );

    if (logSession && logSession.logplex_url) {
      const logStreamResponse = await fetch(logSession.logplex_url);
      if (logStreamResponse.ok) {
        const rawLogs = await logStreamResponse.text();
        fetchedHerokuLogs = rawLogs.split('\\n').filter(line => line.trim() !== '');
        if (fetchedHerokuLogs.length > 0) {
            fetchedHerokuLogs.unshift(`[SYSTEM] Info: ${timestamp} - Successfully fetched ${fetchedHerokuLogs.length} recent log lines from Heroku.`);
        } else {
            fetchedHerokuLogs.push(`[SYSTEM] Info: ${timestamp} - No recent logs returned from Heroku for this app.`);
        }

        const currentDbLogs = deployment.logs || [];
        const combinedLogs = [...currentDbLogs, ...fetchedHerokuLogs].slice(-500); 

        await deploymentsCollection.updateOne(
          { id: deployment.id },
          { $set: { logs: combinedLogs } }
        );
        revalidatePath(`/dashboard/deployments/${deployment.id}`);
        return combinedLogs; 
      } else {
        const errorMsg = `[SYSTEM] Error: ${timestamp} - Failed to fetch logs from Heroku logplex_url. Status: ${logStreamResponse.status}`;
        fetchedHerokuLogs = [errorMsg]; 
      }
    } else {
      const errorMsg = `[SYSTEM] Error: ${timestamp} - Failed to create Heroku log session. Response: ${JSON.stringify(logSession)}`;
      fetchedHerokuLogs = [errorMsg];
    }
  } catch (error: any) {
    console.error(`Error fetching live logs from Heroku for '${deployment.appName}':`, error);
    const errorMsg = `[SYSTEM] Error: ${timestamp} - Exception during Heroku log fetch: ${error.message}`;
    fetchedHerokuLogs = [errorMsg];
  }

  const currentDbLogs = deployment.logs || [];
  const logsToStore = [...currentDbLogs, ...fetchedHerokuLogs].slice(-500);

  await deploymentsCollection.updateOne(
    { id: deployment.id },
    { $set: { logs: logsToStore } }
  );
  revalidatePath(`/dashboard/deployments/${deployment.id}`);
  return logsToStore;
}


export async function controlDeployment(deploymentId: string, action: "start" | "stop" | "restart"): Promise<{ success: boolean; message: string; newStatus?: DeploymentStatus }> {
  const user = await getLoggedInUser();
  if (!user) return { success: false, message: "Unauthorized." };

  const herokuApiKey = await getHerokuApiKey();
  if (!herokuApiKey) return { success: false, message: "Heroku API Key is not configured." };

  const db = await getDb();
  const deploymentsCollection = db.collection<Deployment>("deployments");
  const deployment = await deploymentsCollection.findOne({ id: deploymentId });

  if (!deployment) return { success: false, message: "Deployment not found." };
  if (deployment.userId !== user._id && user.role !== 'admin') {
    return { success: false, message: "Unauthorized to control this deployment." };
  }

  const herokuAppIdOrName = deployment.id; 
  let newDbStatus: DeploymentStatus = deployment.status;
  const timestamp = new Date().toISOString();
  let logMessage = `[SYSTEM] Info: ${timestamp} - User ${user.email} requested Heroku action '${action}' on app '${deployment.appName}'.`;
  let herokuSuccess = false;

  try {
    switch (action) {
      case "start":
        logMessage += ` Attempting to scale web dynos to 1.`;
        await herokuApiCall(`/apps/${herokuAppIdOrName}/formation/web`, 'PATCH', herokuApiKey, { quantity: 1 });
        newDbStatus = 'deploying'; 
        logMessage += ` Heroku accepted scale command. App is starting. Status set to 'deploying'.`;
        herokuSuccess = true;
        break;
      case "stop":
        logMessage += ` Attempting to scale web dynos to 0.`;
        await herokuApiCall(`/apps/${herokuAppIdOrName}/formation/web`, 'PATCH', herokuApiKey, { quantity: 0 });
        newDbStatus = 'stopped';
        logMessage += ` Heroku accepted scale command. App should be stopping. Status set to 'stopped'.`;
        herokuSuccess = true;
        break;
      case "restart":
        logMessage += ` Attempting to restart all dynos.`;
        await herokuApiCall(`/apps/${herokuAppIdOrName}/dynos`, 'DELETE', herokuApiKey);
        newDbStatus = 'deploying'; 
        logMessage += ` Heroku accepted restart command. App is restarting. Status set to 'deploying'.`;
        herokuSuccess = true;
        break;
    }

    if (herokuSuccess) {
      const updatedLogs = [...(deployment.logs || []), logMessage].slice(-500);
      await deploymentsCollection.updateOne(
        { id: deploymentId },
        { $set: { status: newDbStatus, logs: updatedLogs, lastDeployedAt: new Date().toISOString() } } 
      );
      revalidatePath(`/dashboard/deployments/${deploymentId}`);
      return { success: true, message: `Heroku action '${action}' on '${deployment.appName}' initiated. New status: ${newDbStatus}.`, newStatus: newDbStatus };
    } else {
      logMessage += ` Failed to execute Heroku command for '${action}'.`;
      const updatedLogs = [...(deployment.logs || []), logMessage].slice(-500);
      await deploymentsCollection.updateOne({ id: deploymentId }, { $set: {logs: updatedLogs }});
      return { success: false, message: `Failed to execute Heroku command for '${action}'.` };
    }

  } catch (error: any) {
    logMessage += ` Error during Heroku action '${action}': ${error.message}.`;
    const updatedLogs = [...(deployment.logs || []), logMessage].slice(-500);
    const currentDeploymentRecord = await deploymentsCollection.findOne({ id: deploymentId });
    if (currentDeploymentRecord) {
        await deploymentsCollection.updateOne({ id: deploymentId }, { $set: { logs: updatedLogs } });
    }
    console.error(`Error controlling Heroku deployment '${deployment.appName}':`, error);
    return { success: false, message: `Heroku API operation failed: ${error.message}` };
  }
}

export async function deleteDeployment(deploymentId: string): Promise<{ success: boolean; message: string }> {
  const user = await getLoggedInUser();
  if (!user) return { success: false, message: "Unauthorized. Please log in." };

  const herokuApiKey = await getHerokuApiKey();
  if (!herokuApiKey) return { success: false, message: "Heroku API Key is not configured. Cannot delete deployment." };

  const db = await getDb();
  const deploymentsCollection = db.collection<Deployment>("deployments");
  const deployment = await deploymentsCollection.findOne({ id: deploymentId });

  if (!deployment) return { success: false, message: "Deployment not found in our records." };

  if (deployment.userId !== user._id && user.role !== 'admin') {
    return { success: false, message: "Unauthorized. You can only delete your own deployments." };
  }

  const timestamp = new Date().toISOString();
  const initialDbLogs = deployment.logs || [];
  let logMessage = `[SYSTEM] Info: ${timestamp} - User ${user.email} initiated deletion for deployment '${deployment.appName}' (ID: ${deployment.id}).`;
  let herokuAppDeleted = false;


  try {
    logMessage += ` Attempting to delete Heroku app '${deployment.appName}'.`;
    await herokuApiCall(`/apps/${deployment.id}`, 'DELETE', herokuApiKey);
    logMessage += ` Heroku app '${deployment.appName}' delete command accepted by Heroku.`;
    herokuAppDeleted = true;
  } catch (error: any) {
    if (error.message && error.message.includes("(Status: 404)") || (typeof error === 'object' && error !== null && 'status' in error && error.status === 404)) {
      logMessage += ` Heroku app '${deployment.appName}' not found on Heroku (Status 404). Assuming already deleted or never fully created. Proceeding with local cleanup.`;
      herokuAppDeleted = true;
    } else {
      logMessage += ` Error deleting Heroku app '${deployment.appName}': ${error.message}. Local record will NOT be deleted.`;
      const updatedLogs = [...initialDbLogs, logMessage].slice(-500);
      const currentDeploymentRecord = await deploymentsCollection.findOne({ id: deploymentId });
      if (currentDeploymentRecord) {
        await deploymentsCollection.updateOne({ id: deploymentId }, { $set: { logs: updatedLogs } });
        revalidatePath(`/dashboard/deployments/${deploymentId}`);
      }
      return { success: false, message: `Failed to delete Heroku app: ${error.message}.` };
    }
  }

  if (herokuAppDeleted) {
    try {
      const deleteResult = await deploymentsCollection.deleteOne({ id: deployment.id });
      if (deleteResult.deletedCount === 0) {
        logMessage += ` Warning: Heroku app was targeted for deletion, but local record for '${deployment.appName}' not found or already deleted.`;
        console.warn(logMessage);
      } else {
        logMessage += ` Local database record for deployment '${deployment.appName}' deleted successfully.`;
      }
      revalidatePath("/dashboard");
      revalidatePath(`/dashboard/deployments`); 
      return { success: true, message: `Deployment '${deployment.appName}' and associated Heroku app have been successfully deleted (or confirmed deleted).` };
    } catch (dbError: any) {
      logMessage += ` Critical Error: Heroku app '${deployment.appName}' was deleted (or confirmed deleted), but failed to delete local database record: ${dbError.message}. Manual cleanup may be required.`;
      console.error(logMessage);
      return { success: false, message: `Heroku app deleted, but failed to clean up local record: ${dbError.message}` };
    }
  }
  return { success: false, message: "An unknown error occurred during the deletion process." };
}


export async function analyzeDeploymentLogs(logs: string): Promise<{ success: boolean; analysis?: string; error?: string }> {
  if (!logs.trim()) {
    return { success: false, error: "Log content cannot be empty." };
  }
  try {
    const input: AnalyzeDeploymentLogsInput = { deploymentLogs: logs };
    const result = await analyzeLogsFlow(input);
    return { success: true, analysis: result.analysisResult };
  } catch (error) {
    console.error("AI Log Analysis Error:", error);
    return { success: false, error: "Failed to analyze logs. Please try again." };
  }
}

export async function updateDeploymentEnvVariables(
  deploymentId: string,
  newEnvVars: DeploymentFormInput
): Promise<{ success: boolean; message: string }> {
  const user = await getLoggedInUser();
  if (!user) {
    return { success: false, message: "Unauthorized. Please log in." };
  }

  const herokuApiKey = await getHerokuApiKey();
  if (!herokuApiKey) {
    return { success: false, message: "Heroku API Key is not configured." };
  }

  const db = await getDb();
  const deploymentsCollection = db.collection<Deployment>("deployments");
  const deployment = await deploymentsCollection.findOne({ id: deploymentId });

  if (!deployment) {
    return { success: false, message: "Deployment not found." };
  }
  if (deployment.userId !== user._id && user.role !== 'admin') {
    return { success: false, message: "Unauthorized to modify this deployment." };
  }

  const timestamp = new Date().toISOString();
  let logMessage = `[SYSTEM] Info: ${timestamp} - User ${user.email} initiated environment variable update for '${deployment.appName}'.`;

  try {
    const herokuConfigVars = { ...newEnvVars };
    if ('PLATFORM_APP_NAME' in herokuConfigVars) {
      delete (herokuConfigVars as any).PLATFORM_APP_NAME;
    }
    if (!(herokuConfigVars as any).GITHUB_REPO_URL) {
        (herokuConfigVars as any).GITHUB_REPO_URL = deployment.githubRepoUrl || defaultGithubRepoUrl;
    }


    logMessage += ` Attempting to update config vars on Heroku.`;
    await herokuApiCall(`/apps/${deployment.id}/config-vars`, 'PATCH', herokuApiKey, herokuConfigVars);
    logMessage += ` Heroku config vars updated successfully.`;

    logMessage += ` Attempting to restart Heroku app '${deployment.appName}' for changes to take effect.`;
    await herokuApiCall(`/apps/${deployment.id}/dynos`, 'DELETE', herokuApiKey); 
    logMessage += ` Heroku app restart command accepted. Status set to 'deploying'.`;

    const updatedLogs = [...(deployment.logs || []), logMessage].slice(-500);
    await deploymentsCollection.updateOne(
      { id: deployment.id },
      { $set: { envVariables: newEnvVars, logs: updatedLogs, status: 'deploying', lastDeployedAt: new Date().toISOString() } }
    );

    revalidatePath(`/dashboard/deployments/${deployment.id}`);
    return { success: true, message: "Environment variables updated and app restart initiated. Status is 'deploying'." };

  } catch (error: any) {
    logMessage += ` Error during environment variable update or restart: ${error.message}.`;
    const updatedLogs = [...(deployment.logs || []), logMessage].slice(-500);
    const currentDeploymentRecord = await deploymentsCollection.findOne({ id: deploymentId });
    if (currentDeploymentRecord) {
        await deploymentsCollection.updateOne({ id: deployment.id }, { $set: { logs: updatedLogs } });
    }
    console.error(`Error updating env variables for '${deployment.appName}':`, error);
    return { success: false, message: `Failed to update environment variables: ${error.message}` };
  }
}