import { existsSync } from "node:fs";
import type { Hono } from "hono";
import { z } from "zod";
import { join } from "node:path";
import * as envManager from "../env-manager.js";
import type { ZencodeEnv } from "../env-manager.js";
import { containerManager } from "../container-manager.js";
import { imagePullManager } from "../image-pull-manager.js";
import { redactEnvVariables } from "../redact-utils.js";
import { parseBody } from "../validation.js";

// ─── Zod Schemas ──────────────────────────────────────────────────

const envCreateSchema = z.object({
  name: z.string().min(1, "name is required").max(200),
  variables: z.record(z.string(), z.string()).optional().default({}),
  dockerfile: z.string().max(100_000).optional(),
  baseImage: z.string().max(500).optional(),
  ports: z.array(z.number().int().min(1).max(65535)).optional(),
  volumes: z.array(z.string()).optional(),
  initScript: z.string().max(100_000).optional(),
});

const envUpdateSchema = z.object({
  name: z.string().max(200).optional(),
  variables: z.record(z.string(), z.string()).optional(),
  dockerfile: z.string().max(100_000).optional(),
  imageTag: z.string().max(500).optional(),
  baseImage: z.string().max(500).optional(),
  ports: z.array(z.number().int().min(1).max(65535)).optional(),
  volumes: z.array(z.string()).optional(),
  initScript: z.string().max(100_000).optional(),
});

/** Return a copy of the env with sensitive variable values masked. */
function redactEnv(env: ZencodeEnv): ZencodeEnv {
  return { ...env, variables: redactEnvVariables(env.variables) };
}

export function registerEnvRoutes(
  api: Hono,
  options: { webDir: string },
): void {
  api.get("/envs", (c) => {
    try {
      return c.json(envManager.listEnvs().map(redactEnv));
    } catch (e: unknown) {
      return c.json({ error: e instanceof Error ? e.message : String(e) }, 500);
    }
  });

  api.get("/envs/:slug", (c) => {
    const env = envManager.getEnv(c.req.param("slug"));
    if (!env) return c.json({ error: "Environment not found" }, 404);
    return c.json(redactEnv(env));
  });

  api.post("/envs", async (c) => {
    const parsed = await parseBody(c, envCreateSchema);
    if (!parsed.success) return parsed.response;
    const body = parsed.data;
    try {
      const env = envManager.createEnv(body.name, body.variables, {
        dockerfile: body.dockerfile,
        baseImage: body.baseImage,
        ports: body.ports,
        volumes: body.volumes,
        initScript: body.initScript,
      });
      return c.json(redactEnv(env), 201);
    } catch (e: unknown) {
      return c.json({ error: e instanceof Error ? e.message : String(e) }, 400);
    }
  });

  api.put("/envs/:slug", async (c) => {
    const slug = c.req.param("slug");
    const parsed = await parseBody(c, envUpdateSchema);
    if (!parsed.success) return parsed.response;
    const body = parsed.data;
    try {
      const env = envManager.updateEnv(slug, {
        name: body.name,
        variables: body.variables,
        dockerfile: body.dockerfile,
        imageTag: body.imageTag,
        baseImage: body.baseImage,
        ports: body.ports,
        volumes: body.volumes,
        initScript: body.initScript,
      });
      if (!env) return c.json({ error: "Environment not found" }, 404);
      return c.json(redactEnv(env));
    } catch (e: unknown) {
      return c.json({ error: e instanceof Error ? e.message : String(e) }, 400);
    }
  });

  api.delete("/envs/:slug", (c) => {
    const deleted = envManager.deleteEnv(c.req.param("slug"));
    if (!deleted) return c.json({ error: "Environment not found" }, 404);
    return c.json({ ok: true });
  });

  api.post("/envs/:slug/build", async (c) => {
    const slug = c.req.param("slug");
    const env = envManager.getEnv(slug);
    if (!env) return c.json({ error: "Environment not found" }, 404);
    if (!env.dockerfile) return c.json({ error: "No Dockerfile configured for this environment" }, 400);
    if (!(await containerManager.checkDocker())) return c.json({ error: "Docker is not available" }, 503);

    const tag = `zencode-env-${slug}:latest`;
    envManager.updateBuildStatus(slug, "building");

    try {
      const result = await containerManager.buildImageStreaming(env.dockerfile, tag);
      if (result.success) {
        envManager.updateBuildStatus(slug, "success", { imageTag: tag });
        return c.json({ success: true, imageTag: tag, log: result.log });
      } else {
        envManager.updateBuildStatus(slug, "error", { error: result.log.slice(-500) });
        return c.json({ success: false, log: result.log }, 500);
      }
    } catch (e: unknown) {
      const msg = e instanceof Error ? e.message : String(e);
      envManager.updateBuildStatus(slug, "error", { error: msg });
      return c.json({ success: false, error: msg }, 500);
    }
  });

  api.get("/envs/:slug/build-status", (c) => {
    const env = envManager.getEnv(c.req.param("slug"));
    if (!env) return c.json({ error: "Environment not found" }, 404);
    return c.json({
      buildStatus: env.buildStatus || "idle",
      buildError: env.buildError,
      lastBuiltAt: env.lastBuiltAt,
      imageTag: env.imageTag,
    });
  });

  api.post("/docker/build-base", async (c) => {
    if (!(await containerManager.checkDocker())) return c.json({ error: "Docker is not available" }, 503);
    const dockerfilePath = join(options.webDir, "docker", "Dockerfile.zencode");
    if (!existsSync(dockerfilePath)) {
      return c.json({ error: "Base Dockerfile not found at " + dockerfilePath }, 404);
    }
    try {
      const log = await containerManager.buildImage(dockerfilePath, "zencode:latest");
      return c.json({ success: true, log });
    } catch (e: unknown) {
      return c.json({ success: false, error: e instanceof Error ? e.message : String(e) }, 500);
    }
  });

  api.get("/docker/base-image", async (c) => {
    const exists = await containerManager.imageExists("zencode:latest");
    return c.json({ exists, image: "zencode:latest" });
  });

  api.get("/images/:tag/status", async (c) => {
    const tag = decodeURIComponent(c.req.param("tag"));
    if (!tag) return c.json({ error: "Image tag is required" }, 400);
    return c.json(await imagePullManager.getState(tag));
  });

  api.post("/images/:tag/pull", async (c) => {
    const tag = decodeURIComponent(c.req.param("tag"));
    if (!tag) return c.json({ error: "Image tag is required" }, 400);
    if (!(await containerManager.checkDocker())) {
      return c.json({ error: "Docker is not available" }, 503);
    }
    await imagePullManager.pull(tag);
    return c.json({ ok: true, state: await imagePullManager.getState(tag) });
  });
}
