// src/gadget-tasks.ts
// Copyright (C) 2026 Rob Colbert <rob.colbert@openplatform.us>
// Licensed under the Apache License, Version 2.0

import env from "./config/env.ts";

import PlatformService from "./services/platform.ts";
import SchedulerService from "./services/scheduler.ts";
import TaskLockService from "./services/lock.ts";

import { GadgetLog, detectWorkspace, type IDroneRegistration, type IProject } from "@gadget/api";
import { GadgetProcess } from "./lib/process.ts";

const log = new GadgetLog({ name: "GadgetTasks", slug: "gadget-tasks" });

interface UserCredentials {
  email: string;
  password: string;
}

class GadgetTasks extends GadgetProcess {
  private selectedDrone: IDroneRegistration | null = null;
  private startupDir: string | undefined = undefined;

  get name(): string {
    return "GadgetTasks";
  }

  get slug(): string {
    return "gadget-tasks";
  }

  constructor() {
    super();
  }

  async start(): Promise<void> {
    this.hookProcessSignals();

    /*
     * Detect workspace directory by walking up the directory tree.
     * This allows starting from anywhere within a workspace's hierarchy.
     */

    const detection = await detectWorkspace();
    this.startupDir = detection.startupDir;

    if (!detection.found) {
      this.log.error("no workspace found", {
        startupDir: detection.startupDir,
        message: "Start gadget-tasks from within a managed workspace or its subdirectories. No .gadget/workspace.json found in any parent directory.",
      });
      throw new Error(
        "No workspace found. Cannot start gadget-tasks in an unmanaged directory.",
      );
    }

    // Change to the workspace directory for all operations
    const workspaceDir = detection.workspaceDir!;
    process.chdir(workspaceDir);

    this.log.info("workspace detected", {
      workspaceDir,
      startupDir: detection.startupDir,
      navigated: workspaceDir !== detection.startupDir,
    });

    // 1. Acquire singleton lock via Redis
    const lockAcquired = await TaskLockService.acquire();
    if (!lockAcquired) {
      this.log.error("another instance of gadget-tasks is already running");
      process.exit(1);
    }

    // 2. Get user credentials (CLI args or interactive prompt)
    const credentials = await this.getUserCredentials();

    // 3. Authenticate with platform
    await PlatformService.start();
    await PlatformService.authenticate(credentials.email, credentials.password);
    this.log.info("authenticated with platform", { email: credentials.email });

    // 4. Select a drone
    this.selectedDrone = await this.selectDrone();
    PlatformService.setSelectedDrone(this.selectedDrone);
    this.log.info("drone selected", {
      droneId: this.selectedDrone._id,
      hostname: this.selectedDrone.hostname,
    });

    // 5. Connect Socket.IO
    await PlatformService.connectSocket();

    // 6. Start scheduler
    await SchedulerService.start();

    // 7. Fetch and schedule active tasks
    await this.scheduleAllTasks();

    this.log.info(`gadget-tasks v${env.pkg.version} started, ${SchedulerService.scheduledCount} tasks scheduled`);
  }

  async stop(): Promise<number> {
    this.log.info(`gadget-tasks v${env.pkg.version} shutting down`);

    await SchedulerService.stop();
    await PlatformService.stop();
    await TaskLockService.release();

    /*
     * Restore the original startup directory before exiting.
     */
    if (this.startupDir) {
      process.chdir(this.startupDir);
      this.log.info("restored startup directory", { startupDir: this.startupDir });
    }

    this.log.info("shutdown complete");
    return 0;
  }

  /**
   * Query all active projects and schedule their enabled tasks.
   */
  async scheduleAllTasks(): Promise<void> {
    const projects = await PlatformService.getProjects();
    const activeProjects = projects.filter((p) => p.status === "active");

    let taskCount = 0;
    for (const project of activeProjects) {
      for (const task of project.tasks) {
        if (!task.enabled) continue;
        SchedulerService.scheduleTask(task, project as unknown as IProject);
        taskCount++;
      }
    }

    this.log.info("tasks scheduled", {
      projectCount: activeProjects.length,
      taskCount,
    });
  }

  /**
   * Get user credentials from CLI args or interactive prompt.
   * Same pattern as gadget-drone startup.
   */
  async getUserCredentials(): Promise<UserCredentials> {
    const args = process.argv.slice(2);
    const userArg = args.find((a) => a.startsWith("--user="));
    const passArg = args.find((a) => a.startsWith("--password="));

    if (userArg && passArg) {
      return {
        email: userArg.split("=")[1],
        password: passArg.split("=")[1],
      };
    }

    const { input: inqInput, password: inqPassword } = await import("@inquirer/prompts");
    return {
      email: await inqInput({ message: "📧 Enter Email: " }),
      password: await inqPassword({ message: "🔑 Enter Password: " }),
    };
  }

  /**
   * Select a drone for task execution. If only one is available,
   * auto-select it. Otherwise, present an interactive selection.
   */
  async selectDrone(): Promise<IDroneRegistration> {
    const drones = await PlatformService.getAvailableDrones();
    if (drones.length === 0) {
      throw new Error("No available drones. Start a gadget-drone instance first.");
    }
    if (drones.length === 1) {
      this.log.info("auto-selecting only available drone", { droneId: drones[0]._id });
      return drones[0];
    }

    console.log("\nAvailable Drones:");
    drones.forEach((d, i) => {
      console.log(`  ${i + 1}. ${d.hostname} (${d.workspaceId}) - ${d.status}`);
    });

    const { select: inqSelect } = await import("@inquirer/prompts");
    const selection = await inqSelect({
      message: "Select a drone for task execution:",
      choices: drones.map((d) => ({
        name: `${d.hostname} (${d.workspaceId})`,
        value: d,
      })),
    });

    return selection;
  }

  hookProcessSignals(): void {
    process.title = this.name;

    process.on("unhandledRejection", async (error: Error) => {
      this.log.error("Unhandled rejection", { error, stack: error.stack });
      const exitCode = await this.stop();
      process.exit(exitCode);
    });

    process.on("warning", (error) => {
      if (error.name === "DeprecationWarning") return;
      this.log.alert("warning", { error });
    });

    process.on("SIGINT", async () => {
      this.log.info("SIGINT received");
      const exitCode = await this.stop();
      process.exit(exitCode);
    });
  }
}

(async () => {
  try {
    const tasks = new GadgetTasks();
    await tasks.start();
  } catch (error) {
    console.error("failed to start gadget-tasks", error);
    process.exit(-1);
  }
})();
