/**
 * Railway adapter.
 *
 * Emits `railway.json` (config-as-code) + `nixpacks.toml` so Railway's
 * default Nixpacks builder uses Bun instead of the Node detection
 * fallback. Without nixpacks.toml the builder tries to run `npm install`
 * and fails on Bun-only projects.
 *
 * References:
 *   - https://docs.railway.com/reference/config-as-code
 *   - https://docs.railway.com/reference/nixpacks
 *
 * @module cli/commands/deploy/adapters/railway
 */
import path from "node:path";
import { CLI_ERROR_CODES } from "../../../errors/codes";
import { writeArtifact } from "../artifact-writer";
import {
  getProviderCliStatus,
  type SpawnVersion,
} from "../provider-cli";
import type {
  AdapterArtifact,
  AdapterCheckResult,
  AdapterIssue,
  DeployAdapter,
  DeployOptions,
  DeployResult,
  ProjectContext,
  SecretSpec,
} from "../types";

const MIN_RAILWAY_VERSION = "3.0.0";
const DEFAULT_PORT = 3333;

const RAILWAY_SECRETS: ReadonlyArray<SecretSpec> = [
  {
    name: "RAILWAY_TOKEN",
    required: true,
    description: "Railway project token for unattended deploys.",
    docsUrl: "https://docs.railway.com/guides/cli#tokens",
  },
];

// ---------------------------------------------------------------------
// railway.json template
// ---------------------------------------------------------------------

export interface RailwayJsonOptions {
  projectName: string;
  /** Build command to run in the builder image. */
  buildCommand?: string;
  /** Start command Railway uses at runtime. */
  startCommand?: string;
  /**
   * Healthcheck path. Railway runs a GET here before marking the deploy
   * healthy. Defaults to `/` for Mandu apps.
   */
  healthcheckPath?: string;
  /** Replicas (default 1). Railway free tier is 1. */
  numReplicas?: number;
  /** Restart policy when the container exits. */
  restartPolicyType?: "ON_FAILURE" | "ALWAYS" | "NEVER";
  /** Env vars exposed to the container (non-secret). */
  env?: Record<string, string>;
}

export function renderRailwayJson(options: RailwayJsonOptions): string {
  const config: Record<string, unknown> = {
    $schema: "https://railway.com/railway.schema.json",
    build: {
      builder: "NIXPACKS",
      buildCommand: options.buildCommand ?? "bun run build",
    },
    deploy: {
      startCommand: options.startCommand ?? "bun run mandu start",
      healthcheckPath: options.healthcheckPath ?? "/",
      healthcheckTimeout: 300,
      restartPolicyType: options.restartPolicyType ?? "ON_FAILURE",
      restartPolicyMaxRetries: 10,
      numReplicas: options.numReplicas ?? 1,
    },
  };
  if (options.env && Object.keys(options.env).length > 0) {
    for (const key of Object.keys(options.env)) {
      if (!/^[A-Z][A-Z0-9_]*$/.test(key)) {
        throw new Error(
          `renderRailwayJson: env var "${key}" must match /^[A-Z][A-Z0-9_]*$/`
        );
      }
    }
    (config as { environments?: Record<string, unknown> }).environments = {
      production: { variables: options.env },
    };
  }
  // Consume projectName so the test suite can validate it even if the
  // generated JSON doesn't emit the field (Railway reads it from the
  // service, not from config). Stash it in a comment-like key so the
  // user sees it in the diff.
  config.__manduProjectHint = options.projectName;
  return `${JSON.stringify(config, null, 2)}\n`;
}

// ---------------------------------------------------------------------
// nixpacks.toml template
// ---------------------------------------------------------------------

export interface NixpacksTomlOptions {
  /** Bun version tag (matches image in Dockerfile). */
  bunVersion?: string;
  /** PORT env override. */
  port?: number;
  /** Extra providers (e.g. ["node"] — rarely needed). */
  providers?: string[];
}

export function renderNixpacksToml(options: NixpacksTomlOptions = {}): string {
  const bunVersion = options.bunVersion ?? "1.3.12";
  const port = options.port ?? DEFAULT_PORT;
  const providers = options.providers ?? ["bun"];
  const lines = [
    "# Generated by `mandu deploy --target=railway`.",
    "# Docs: https://nixpacks.com/docs/configuration/file",
    "",
    `providers = ${JSON.stringify(providers)}`,
    "",
    "[phases.setup]",
    `  nixPkgs = ["bun"]`,
    `  cmds = ["bun --version"]`,
    "",
    "[phases.install]",
    `  cmds = ["bun install --frozen-lockfile"]`,
    "",
    "[phases.build]",
    `  cmds = ["bun run build"]`,
    "",
    "[start]",
    `  cmd = "bun run mandu start"`,
    "",
    "[variables]",
    `  PORT = "${port}"`,
    `  BUN_VERSION = "${bunVersion}"`,
    "",
  ];
  return lines.join("\n");
}

// ---------------------------------------------------------------------
// Adapter
// ---------------------------------------------------------------------

interface RailwayAdapterOptions {
  spawnImpl?: SpawnVersion;
  deployImpl?: (
    project: ProjectContext,
    options: DeployOptions
  ) => Promise<DeployResult>;
}

export function createRailwayAdapter(
  internal: RailwayAdapterOptions = {}
): DeployAdapter {
  return {
    name: "Railway",
    target: "railway",
    minimumCliVersion: { binary: "railway", semver: MIN_RAILWAY_VERSION },
    secrets: RAILWAY_SECRETS,

    async check(project, options): Promise<AdapterCheckResult> {
      const errors: AdapterIssue[] = [];
      const warnings: AdapterIssue[] = [];

      if (options.execute) {
        const status = await getProviderCliStatus("railway", MIN_RAILWAY_VERSION, {
          spawnImpl: internal.spawnImpl,
        });
        if (!status.installed) {
          errors.push({
            code: CLI_ERROR_CODES.DEPLOY_PROVIDER_CLI_MISSING,
            message: "railway CLI is not installed.",
            hint: "Install from https://docs.railway.com/guides/cli",
          });
        } else if (!status.meetsMinimum && status.version) {
          errors.push({
            code: CLI_ERROR_CODES.DEPLOY_PROVIDER_CLI_OUTDATED,
            message: `railway ${status.version} is older than required ${MIN_RAILWAY_VERSION}.`,
            hint: "Upgrade with your package manager (brew/npm/scoop).",
          });
        }
      }

      void project;
      return { ok: errors.length === 0, errors, warnings };
    },

    async prepare(project, options): Promise<AdapterArtifact[]> {
      const artifacts: AdapterArtifact[] = [];
      const rootDir = project.rootDir;
      const projectName = options.projectName ?? project.projectName;

      const railwayJsonResult = await writeArtifact({
        forbiddenValues: options.forbiddenSecrets,
        path: path.join(rootDir, "railway.json"),
        content: renderRailwayJson({ projectName }),
        preserveIfExists: true,
      });
      artifacts.push({
        path: railwayJsonResult.path,
        preserved: railwayJsonResult.preserved,
        description: railwayJsonResult.preserved
          ? "Existing railway.json preserved"
          : `Scaffolded railway.json (project=${projectName})`,
      });

      const nixpacksResult = await writeArtifact({
        forbiddenValues: options.forbiddenSecrets,
        path: path.join(rootDir, "nixpacks.toml"),
        content: renderNixpacksToml({
          port: project.config.server?.port ?? DEFAULT_PORT,
        }),
        preserveIfExists: true,
      });
      artifacts.push({
        path: nixpacksResult.path,
        preserved: nixpacksResult.preserved,
        description: "Nixpacks config pinning Bun as the provider",
      });

      return artifacts;
    },

    async deploy(project, options): Promise<DeployResult> {
      if (internal.deployImpl) {
        return internal.deployImpl(project, options);
      }
      return {
        ok: false,
        errors: [
          {
            code: CLI_ERROR_CODES.DEPLOY_NOT_IMPLEMENTED,
            message: "railway spawn harness is provided only via DeployAdapter.deployImpl.",
            hint: "Run `railway up` manually after `mandu deploy --target=railway`.",
          },
        ],
      };
    },
  };
}

export const railwayAdapter: DeployAdapter = createRailwayAdapter();
