import { Router, type Request, type Response } from "express";
import { randomUUID } from "crypto";
import { db, bootstrapJobs, invoices, type BootstrapJob } from "@workspace/db";
import { eq, and } from "drizzle-orm";
import { lnbitsService } from "../lib/lnbits.js";
import { pricingService } from "../lib/pricing.js";
import { provisionerService } from "../lib/provisioner.js";
import { makeLogger } from "../lib/logger.js";
// Assuming a Zod schema for request body and params will be created
// import { CreateBootstrapJobBody, GetBootstrapJobParams } from "@workspace/api-zod";

const logger = makeLogger("bootstrap-routes");

const router = Router();

async function getBootstrapJobById(id: string): Promise<BootstrapJob | null> {
  const rows = await db.select().from(bootstrapJobs).where(eq(bootstrapJobs.id, id)).limit(1);
  return rows[0] ?? null;
}

async function getInvoiceById(id: string) {
  const rows = await db.select().from(invoices).where(eq(invoices.id, id)).limit(1);
  return rows[0] ?? null;
}

/**
 * Runs the node provisioning in a background task so HTTP polls return fast.
 */
async function runProvisioningInBackground(jobId: string): Promise<void> {
  try {
    logger.info("starting node provisioning", { jobId });
    await db.update(bootstrapJobs).set({ state: "provisioning", updatedAt: new Date() }).where(eq(bootstrapJobs.id, jobId));

    const provisionResult = await provisionerService.provisionNode(jobId);

    await db
      .update(bootstrapJobs)
      .set({
        state: "ready",
        dropletId: provisionResult.dropletId,
        nodeIp: provisionResult.nodeIp,
        tailscaleHostname: provisionResult.tailscaleHostname,
        lnbitsUrl: provisionResult.lnbitsUrl,
        sshPrivateKey: provisionResult.sshPrivateKey, // Stored once, cleared after delivery
        updatedAt: new Date(),
      })
      .where(eq(bootstrapJobs.id, jobId));

    logger.info("node provisioning complete", { jobId, dropletId: provisionResult.dropletId });
  } catch (err) {
    const message = err instanceof Error ? err.message : "Node provisioning error";
    logger.error("node provisioning failed", { jobId, error: message });
    await db
      .update(bootstrapJobs)
      .set({ state: "failed", errorMessage: message, updatedAt: new Date() })
      .where(eq(bootstrapJobs.id, jobId));
  }
}

/**
 * Checks whether the bootstrap invoice has been paid and, if so,
 * advances the state machine.
 */
async function advanceBootstrapJob(job: BootstrapJob): Promise<BootstrapJob | null> {
  if (job.state === "awaiting_payment") {
    // Assuming invoice details are directly on the bootstrapJob, not a separate invoice table
    // If a separate invoice entry is needed, uncomment the invoice related logic from jobs.ts
    const isPaid = await lnbitsService.checkInvoicePaid(job.paymentHash);
    if (!isPaid) return job;

    const advanced = await db.transaction(async (tx) => {
      // For now, we update the bootstrap job directly. If we had a separate `invoices` table
      // linked to bootstrap jobs, we would update that too.
      const updated = await tx
        .update(bootstrapJobs)
        .set({ state: "provisioning", updatedAt: new Date() })
        .where(and(eq(bootstrapJobs.id, job.id), eq(bootstrapJobs.state, "awaiting_payment")))
        .returning();
      return updated.length > 0;
    });

    if (!advanced) return getBootstrapJobById(job.id);

    logger.info("bootstrap invoice paid", { bootstrapJobId: job.id, paymentHash: job.paymentHash });

    // Fire provisioning in background — poll returns immediately with "provisioning"
    setImmediate(() => { void runProvisioningInBackground(job.id); });

    return getBootstrapJobById(job.id);
  }

  return job;
}

// ── POST /api/bootstrap ──────────────────────────────────────────────────────

router.post("/bootstrap", async (req: Request, res: Response) => {
  // No request body for now, just trigger bootstrap
  try {
    const bootstrapFeeSats = pricingService.calculateBootstrapFeeSats();
    const jobId = randomUUID();
    const createdAt = new Date();

    const lnbitsInvoice = await lnbitsService.createInvoice(bootstrapFeeSats, `Node bootstrap fee for job ${jobId}`);

    await db.insert(bootstrapJobs).values({
      id: jobId,
      state: "awaiting_payment",
      amountSats: bootstrapFeeSats,
      paymentHash: lnbitsInvoice.paymentHash,
      paymentRequest: lnbitsInvoice.paymentRequest,
      createdAt,
      updatedAt: createdAt,
    });

    logger.info("bootstrap job created", {
      jobId,
      amountSats: bootstrapFeeSats,
      stubMode: lnbitsService.stubMode,
    });

    res.status(201).json({
      jobId,
      createdAt: createdAt.toISOString(),
      bootstrapInvoice: {
        paymentRequest: lnbitsInvoice.paymentRequest,
        amountSats: bootstrapFeeSats,
        paymentHash: lnbitsInvoice.paymentHash,
      },
    });
  } catch (err) {
    const message = err instanceof Error ? err.message : "Failed to create bootstrap job";
    logger.error("bootstrap job creation failed", { error: message });
    res.status(500).json({ error: message });
  }
});

// ── GET /api/bootstrap/:id ───────────────────────────────────────────────────

router.get("/bootstrap/:id", async (req: Request, res: Response) => {
  const { id } = req.params; // Assuming ID is always valid, add Zod validation later

  try {
    let job = await getBootstrapJobById(id);
    if (!job) { res.status(404).json({ error: "Bootstrap job not found" }); return; }

    const advanced = await advanceBootstrapJob(job);
    if (advanced) job = advanced;

    // Remove SSH private key from response if it has been delivered
    const sshPrivateKey = job.sshPrivateKey && !job.sshKeyDelivered ? job.sshPrivateKey : undefined;

    res.json({
      jobId: job.id,
      state: job.state,
      createdAt: job.createdAt.toISOString(),
      updatedAt: job.updatedAt.toISOString(),
      amountSats: job.amountSats,
      ...(job.state === "awaiting_payment" ? {
        bootstrapInvoice: {
          paymentRequest: job.paymentRequest,
          amountSats: job.amountSats,
          paymentHash: job.paymentHash,
        },
      } : {}),
      ...(job.state === "ready" ? {
        dropletId: job.dropletId,
        nodeIp: job.nodeIp,
        tailscaleHostname: job.tailscaleHostname,
        lnbitsUrl: job.lnbitsUrl,
        sshPrivateKey: sshPrivateKey, // Only return if not yet delivered
        sshKeyDelivered: job.sshKeyDelivered,
      } : {}),
      ...(job.state === "failed" ? { errorMessage: job.errorMessage } : {}),
    });

    // Mark SSH key as delivered after it's returned to the user once
    if (job.sshPrivateKey && !job.sshKeyDelivered && job.state === "ready") {
      await db.update(bootstrapJobs).set({ sshKeyDelivered: true, updatedAt: new Date() }).where(eq(bootstrapJobs.id, id));
      logger.info("SSH private key marked as delivered", { jobId: job.id });
    }

  } catch (err) {
    const message = err instanceof Error ? err.message : "Failed to fetch bootstrap job";
    logger.error("bootstrap job fetch failed", { error: message });
    res.status(500).json({ error: message });
  }
});

export default router;