import {
  readdirSync,
  readFileSync,
  unlinkSync,
  existsSync,
} from "node:fs";
import { join } from "node:path";
import { ensureSecureDir, writeSecureFile } from "./fs-permissions.js";

// â”€â”€â”€ Types â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€

export interface ZencodeEnv {
  name: string;
  slug: string;
  variables: Record<string, string>;

  // Docker configuration
  /** Raw Dockerfile content (stored inline). When present, used to build a custom image. */
  dockerfile?: string;
  /** Tag of the built image (e.g. "zencode-env-myproject:latest") */
  imageTag?: string;
  /** Base image to use when no custom Dockerfile is provided (e.g. "zencode:latest") */
  baseImage?: string;
  /** Current build status */
  buildStatus?: "idle" | "building" | "success" | "error";
  /** Last build error message */
  buildError?: string;
  /** Timestamp of last successful build */
  lastBuiltAt?: number;
  /** Container ports to expose */
  ports?: number[];
  /** Extra volume mounts in "host:container[:opts]" format */
  volumes?: string[];
  /** Shell script to run inside the container before the CLI session starts */
  initScript?: string;

  createdAt: number;
  updatedAt: number;
}

/** Fields that can be updated via the update API */
export interface EnvUpdateFields {
  name?: string;
  variables?: Record<string, string>;
  dockerfile?: string;
  imageTag?: string;
  baseImage?: string;
  ports?: number[];
  volumes?: string[];
  initScript?: string;
}

import { ZENCODE_DIR } from "./config.js";

// â”€â”€â”€ Paths â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€

const ENVS_DIR = join(ZENCODE_DIR, "envs");

function ensureDir(): void {
  ensureSecureDir(ENVS_DIR);
}

function filePath(slug: string): string {
  return join(ENVS_DIR, `${slug}.json`);
}

// â”€â”€â”€ Validation â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€

const MAX_ENV_NAME_LENGTH = 100;
const MAX_VARIABLE_KEY_LENGTH = 256;
const MAX_VARIABLE_VALUE_LENGTH = 65_536; // 64 KB
const MAX_INIT_SCRIPT_LENGTH = 65_536; // 64 KB
const MAX_DOCKERFILE_LENGTH = 262_144; // 256 KB
const MAX_ENV_COUNT = 100;
const VALID_VAR_KEY_REGEX = /^[A-Za-z_][A-Za-z0-9_]*$/;

function validateEnvData(data: {
  name?: string;
  variables?: Record<string, string>;
  initScript?: string;
  dockerfile?: string;
}): void {
  if (data.name !== undefined && data.name.length > MAX_ENV_NAME_LENGTH) {
    throw new Error(
      `Environment name too long (max ${MAX_ENV_NAME_LENGTH} characters)`,
    );
  }

  if (data.variables) {
    for (const [key, value] of Object.entries(data.variables)) {
      if (!VALID_VAR_KEY_REGEX.test(key)) {
        throw new Error(
          `Invalid variable name "${key}" â€” must start with a letter or underscore and contain only alphanumerics and underscores`,
        );
      }
      if (key.length > MAX_VARIABLE_KEY_LENGTH) {
        throw new Error(
          `Variable key "${key}" too long (max ${MAX_VARIABLE_KEY_LENGTH} characters)`,
        );
      }
      if (typeof value === "string" && value.length > MAX_VARIABLE_VALUE_LENGTH) {
        throw new Error(
          `Variable value for "${key}" too long (max ${MAX_VARIABLE_VALUE_LENGTH / 1024}KB)`,
        );
      }
    }
  }

  if (
    data.initScript !== undefined &&
    data.initScript.length > MAX_INIT_SCRIPT_LENGTH
  ) {
    throw new Error(
      `Init script too long (max ${MAX_INIT_SCRIPT_LENGTH / 1024}KB)`,
    );
  }

  if (
    data.dockerfile !== undefined &&
    data.dockerfile.length > MAX_DOCKERFILE_LENGTH
  ) {
    throw new Error(
      `Dockerfile too long (max ${MAX_DOCKERFILE_LENGTH / 1024}KB)`,
    );
  }
}

// â”€â”€â”€ Helpers â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€

function slugify(name: string): string {
  return name
    .toLowerCase()
    .replace(/\s+/g, "-")
    .replace(/[^a-z0-9-]/g, "")
    .replace(/-+/g, "-")
    .replace(/^-|-$/g, "");
}

// â”€â”€â”€ CRUD â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€

export function listEnvs(): ZencodeEnv[] {
  ensureDir();
  try {
    const files = readdirSync(ENVS_DIR).filter((f) => f.endsWith(".json"));
    const envs: ZencodeEnv[] = [];
    for (const file of files) {
      try {
        const raw = readFileSync(join(ENVS_DIR, file), "utf-8");
        envs.push(JSON.parse(raw));
      } catch {
        // Skip corrupt files
      }
    }
    envs.sort((a, b) => a.name.localeCompare(b.name));
    return envs;
  } catch {
    return [];
  }
}

export function getEnv(slug: string): ZencodeEnv | null {
  ensureDir();
  try {
    const raw = readFileSync(filePath(slug), "utf-8");
    return JSON.parse(raw) as ZencodeEnv;
  } catch {
    return null;
  }
}

/**
 * Return the effective Docker image for an environment.
 * Priority: imageTag (custom built) > baseImage (user-selected) > default.
 */
export function getEffectiveImage(slug: string): string | null {
  const env = getEnv(slug);
  if (!env) return null;
  return env.imageTag || env.baseImage || null;
}

export function createEnv(
  name: string,
  variables: Record<string, string> = {},
  docker?: {
    dockerfile?: string;
    baseImage?: string;
    ports?: number[];
    volumes?: string[];
    initScript?: string;
  },
): ZencodeEnv {
  if (!name || !name.trim()) throw new Error("Environment name is required");

  validateEnvData({
    name: name.trim(),
    variables,
    initScript: docker?.initScript,
    dockerfile: docker?.dockerfile,
  });

  const slug = slugify(name.trim());
  if (!slug) throw new Error("Environment name must contain alphanumeric characters");

  ensureDir();

  if (listEnvs().length >= MAX_ENV_COUNT) {
    throw new Error(`Maximum environment count reached (${MAX_ENV_COUNT})`);
  }

  if (existsSync(filePath(slug))) {
    throw new Error(`An environment with a similar name already exists ("${slug}")`);
  }

  const now = Date.now();
  const env: ZencodeEnv = {
    name: name.trim(),
    slug,
    variables,
    createdAt: now,
    updatedAt: now,
  };

  // Apply Docker config if provided
  if (docker) {
    if (docker.dockerfile !== undefined) env.dockerfile = docker.dockerfile;
    if (docker.baseImage !== undefined) env.baseImage = docker.baseImage;
    if (docker.ports !== undefined) env.ports = docker.ports;
    if (docker.volumes !== undefined) env.volumes = docker.volumes;
    if (docker.initScript !== undefined) env.initScript = docker.initScript;
  }

  writeSecureFile(filePath(slug), JSON.stringify(env, null, 2));
  return env;
}

export function updateEnv(
  slug: string,
  updates: EnvUpdateFields,
): ZencodeEnv | null {
  ensureDir();
  const existing = getEnv(slug);
  if (!existing) return null;

  validateEnvData({
    name: updates.name?.trim(),
    variables: updates.variables,
    initScript: updates.initScript,
    dockerfile: updates.dockerfile,
  });

  const newName = updates.name?.trim() || existing.name;
  const newSlug = slugify(newName);
  if (!newSlug) throw new Error("Environment name must contain alphanumeric characters");

  // If name changed, check for slug collision with a different env
  if (newSlug !== slug && existsSync(filePath(newSlug))) {
    throw new Error(`An environment with a similar name already exists ("${newSlug}")`);
  }

  const env: ZencodeEnv = {
    ...existing,
    name: newName,
    slug: newSlug,
    variables: updates.variables ?? existing.variables,
    updatedAt: Date.now(),
  };

  // Apply Docker field updates (only override if explicitly provided)
  if (updates.dockerfile !== undefined) env.dockerfile = updates.dockerfile;
  if (updates.imageTag !== undefined) env.imageTag = updates.imageTag;
  if (updates.baseImage !== undefined) env.baseImage = updates.baseImage;
  if (updates.ports !== undefined) env.ports = updates.ports;
  if (updates.volumes !== undefined) env.volumes = updates.volumes;
  if (updates.initScript !== undefined) env.initScript = updates.initScript;

  // If slug changed, delete old file
  if (newSlug !== slug) {
    try { unlinkSync(filePath(slug)); } catch { /* ok */ }
  }

  writeSecureFile(filePath(newSlug), JSON.stringify(env, null, 2));
  return env;
}

/**
 * Update the build status fields of an environment.
 * Used during Docker image builds to track progress.
 */
export function updateBuildStatus(
  slug: string,
  status: ZencodeEnv["buildStatus"],
  opts?: { error?: string; imageTag?: string },
): ZencodeEnv | null {
  ensureDir();
  const existing = getEnv(slug);
  if (!existing) return null;

  existing.buildStatus = status;
  existing.updatedAt = Date.now();

  if (opts?.error !== undefined) existing.buildError = opts.error;
  if (opts?.imageTag !== undefined) existing.imageTag = opts.imageTag;
  if (status === "success") {
    existing.lastBuiltAt = Date.now();
    existing.buildError = undefined;
  }

  writeSecureFile(filePath(slug), JSON.stringify(existing, null, 2));
  return existing;
}

export function deleteEnv(slug: string): boolean {
  ensureDir();
  if (!existsSync(filePath(slug))) return false;
  try {
    unlinkSync(filePath(slug));
    return true;
  } catch {
    return false;
  }
}
