// src/index.ts
// Remote transcoder worker entry point.
//
// Workflow for each job:
//   1. Claim job via CMS API
//   2. Download source MP4
//   3. Transcode to HLS (FFmpeg – identical to VPS transcoder)
//   4. Zip the HLS output
//   5. Upload zip to CMS
//   6. Repeat until queue is empty, then exit 0.

import * as fs from "fs/promises";
import * as path from "path";
import { TranscoderApiClient } from "./api-client";
import { transcodeToHls } from "./transcoder";
import { createZipArchive } from "./packager";

// ---------------------------------------------------------------------------
// Environment validation
// ---------------------------------------------------------------------------

function requireEnv(name: string): string {
  const val = process.env[name];
  if (!val) {
    console.error(`[Fatal] Missing required environment variable: ${name}`);
    process.exit(1);
  }
  return val;
}

const CMS_URL          = requireEnv("CMS_URL").replace(/\/$/, "");
const TRANSCODER_SECRET = requireEnv("TRANSCODER_SECRET");
const WORK_DIR         = (process.env.WORK_DIR ?? "/work").replace(/\/$/, "");

// ---------------------------------------------------------------------------
// Formatting helpers
// ---------------------------------------------------------------------------

function fmtBytes(bytes: number): string {
  if (bytes >= 1024 ** 3) return `${(bytes / 1024 ** 3).toFixed(1)} GB`;
  if (bytes >= 1024 ** 2) return `${(bytes / 1024 ** 2).toFixed(0)} MB`;
  return `${(bytes / 1024).toFixed(0)} KB`;
}

function fmtDuration(ms: number): string {
  const s = Math.floor(ms / 1000);
  const m = Math.floor(s / 60);
  const h = Math.floor(m / 60);
  if (h > 0) return `${h}h ${m % 60}m ${s % 60}s`;
  if (m > 0) return `${m}m ${s % 60}s`;
  return `${s}s`;
}

async function safeRm(...paths: string[]): Promise<void> {
  for (const p of paths) {
    await fs.rm(p, { recursive: true, force: true }).catch(() => {});
  }
}

// ---------------------------------------------------------------------------
// Single job handler
// ---------------------------------------------------------------------------

async function processJob(
  client: TranscoderApiClient,
  videoId: string,
  downloadUrl: string
): Promise<void> {
  const jobStart = Date.now();

  const jobDir  = path.join(WORK_DIR, videoId);
  const inputMp4 = path.join(jobDir, "input.mp4");
  const hlsDir   = path.join(jobDir, "hls");
  const zipPath  = path.join(jobDir, "output.zip");

  console.log(`\n[Job] ${videoId}`);

  try {
    await fs.mkdir(hlsDir, { recursive: true });

    // 1. Download --------------------------------------------------------
    console.log("[Download] Starting...");
    const dlStart = Date.now();
    let lastReported = 0;

    const downloadedBytes = await client.downloadVideo(
      downloadUrl,
      inputMp4,
      (received) => {
        if (received - lastReported >= 100 * 1024 * 1024) {
          lastReported = received;
          process.stdout.write(`\r[Download] ${fmtBytes(received)} received...`);
        }
      }
    );

    process.stdout.write("\n");
    console.log(
      `[Download] ${fmtBytes(downloadedBytes)} in ${fmtDuration(Date.now() - dlStart)}`
    );

    // 2. Transcode -------------------------------------------------------
    console.log("[Transcode] Starting...");
    const txStart = Date.now();
    const result  = await transcodeToHls(inputMp4, hlsDir);
    const txMs    = Date.now() - txStart;

    console.log(
      `[Transcode] ${result.variants.join(", ")} in ${fmtDuration(txMs)}`
    );

    // 3. Package ---------------------------------------------------------
    console.log("[Package] Creating zip...");
    const pkgStart  = Date.now();
    const zipBytes  = await createZipArchive(hlsDir, zipPath);
    console.log(
      `[Package] ${fmtBytes(zipBytes)} in ${fmtDuration(Date.now() - pkgStart)}`
    );

    // 4. Upload ----------------------------------------------------------
    console.log("[Upload] Starting...");
    const ulStart = Date.now();
    await client.uploadHls(videoId, zipPath);
    console.log(`[Upload] Done in ${fmtDuration(Date.now() - ulStart)}`);

    console.log(`[Complete] ${videoId} in ${fmtDuration(Date.now() - jobStart)}`);
  } finally {
    // Always clean up local work directory.
    await safeRm(jobDir);
  }
}

// ---------------------------------------------------------------------------
// Main loop
// ---------------------------------------------------------------------------

async function main(): Promise<void> {
  console.log("[Worker] Remote Transcoder starting");
  console.log(`[Worker] CMS     : ${CMS_URL}`);
  console.log(`[Worker] Work dir: ${WORK_DIR}`);

  await fs.mkdir(WORK_DIR, { recursive: true });

  const client    = new TranscoderApiClient(CMS_URL, TRANSCODER_SECRET);
  let processed   = 0;

  while (true) {
    const job = await client.claimJob();

    if (!job) {
      if (processed === 0) {
        console.log("\n[Worker] No jobs queued – nothing to do.");
      } else {
        console.log(`\n[Worker] Queue empty. Jobs processed this run: ${processed}`);
      }
      break;
    }

    try {
      await processJob(client, job.videoId, job.downloadUrl);
      processed++;
    } catch (err) {
      const msg = err instanceof Error ? err.message : String(err);
      console.error(`[Error] ${job.videoId}: ${msg}`);
      await client.markFailed(job.videoId, msg);
    }
  }

  console.log("[Worker] Exiting.");
}

main().catch((err) => {
  console.error("[Fatal]", err);
  process.exit(1);
});
