/**
 * Netlify adapter.
 *
 * Emits `netlify.toml` + a `netlify/functions/ssr.ts` function wrapper so
 * Mandu SSR runs on Netlify's Node runtime. Phase 15.3 will add a Deno
 * Edge Functions target (currently unavailable — Mandu's core uses Node
 * built-ins not all of which have Deno shims).
 *
 * References:
 *   - https://docs.netlify.com/configure-builds/file-based-configuration/
 *   - https://docs.netlify.com/functions/overview/
 *
 * @module cli/commands/deploy/adapters/netlify
 */
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_NETLIFY_VERSION = "17.0.0";

const NETLIFY_SECRETS: ReadonlyArray<SecretSpec> = [
  {
    name: "NETLIFY_AUTH_TOKEN",
    required: true,
    description: "Netlify personal access token for `netlify deploy`.",
    docsUrl: "https://app.netlify.com/user/applications#personal-access-tokens",
  },
];

// ---------------------------------------------------------------------
// netlify.toml template
// ---------------------------------------------------------------------

export interface NetlifyTomlOptions {
  projectName: string;
  buildCommand?: string;
  publishDir?: string;
  functionsDir?: string;
  /** Node version for the functions runtime. Netlify supports 18 / 20 / 22. */
  nodeVersion?: "18" | "20" | "22";
  /** Runtime env (non-secret). Secrets belong in `netlify env:set`. */
  env?: Record<string, string>;
}

export function renderNetlifyToml(options: NetlifyTomlOptions): string {
  if (!/^[a-z0-9][a-z0-9-]{0,62}$/i.test(options.projectName)) {
    throw new Error(
      `renderNetlifyToml: projectName "${options.projectName}" is invalid`
    );
  }
  const publishDir = options.publishDir ?? ".mandu/client";
  const functionsDir = options.functionsDir ?? "netlify/functions";
  const buildCommand = options.buildCommand ?? "bun run build";
  const nodeVersion = options.nodeVersion ?? "20";

  const lines: string[] = [
    "# Generated by `mandu deploy --target=netlify`.",
    "# Docs: https://docs.netlify.com/configure-builds/file-based-configuration/",
    "",
    "[build]",
    `  command = "${buildCommand}"`,
    `  publish = "${publishDir}"`,
    `  functions = "${functionsDir}"`,
    "",
    "[build.environment]",
    `  NODE_VERSION = "${nodeVersion}"`,
    '  NPM_FLAGS = "--no-audit --no-fund"',
    '  BUN_INSTALL = "/opt/buildhome/.bun"',
    "",
    "# Pass every dynamic request through the SSR function. Static assets",
    "# in `publish` are served from the CDN first.",
    "[[redirects]]",
    '  from = "/*"',
    '  to = "/.netlify/functions/ssr"',
    "  status = 200",
    "  force = false",
    "",
    "[functions]",
    '  node_bundler = "esbuild"',
    `  included_files = [".mandu/**"]`,
  ];

  if (options.env && Object.keys(options.env).length > 0) {
    lines.push("", "[build.environment]");
    for (const [key, value] of Object.entries(options.env)) {
      if (!/^[A-Z][A-Z0-9_]*$/.test(key)) {
        throw new Error(
          `renderNetlifyToml: env var "${key}" must match /^[A-Z][A-Z0-9_]*$/`
        );
      }
      lines.push(`  ${key} = ${JSON.stringify(value)}`);
    }
  }

  return lines.join("\n") + "\n";
}

// ---------------------------------------------------------------------
// SSR function template — `netlify/functions/ssr.ts`
// ---------------------------------------------------------------------

export function renderNetlifySsrFunction(): string {
  return `// Generated by \`mandu deploy --target=netlify\`.
// Netlify Node function wrapping Mandu's SSR handler. Every non-static
// request lands here via the catch-all redirect in netlify.toml.

import type { Context } from "@netlify/functions";
import { generateManifest, startServer, registerManifestHandlers } from "@mandujs/core";

let cachedFetch: ((req: Request) => Promise<Response>) | null = null;

async function getFetch(): Promise<(req: Request) => Promise<Response>> {
  if (cachedFetch) return cachedFetch;
  const rootDir = process.cwd();
  const { manifest } = await generateManifest(rootDir);
  await registerManifestHandlers(manifest, rootDir, {
    importFn: (p: string) => import(p),
    registeredLayouts: new Set(),
  });
  const server = startServer(manifest, {
    port: 0,
    rootDir,
    isDev: false,
  });
  cachedFetch = (req) => server.server.fetch(req);
  return cachedFetch;
}

export default async function handler(req: Request, _ctx: Context): Promise<Response> {
  const fetchHandler = await getFetch();
  return fetchHandler(req);
}

export const config = {
  path: "/*",
};
`;
}

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

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

export function createNetlifyAdapter(
  internal: NetlifyAdapterOptions = {}
): DeployAdapter {
  return {
    name: "Netlify",
    target: "netlify",
    minimumCliVersion: { binary: "netlify", semver: MIN_NETLIFY_VERSION },
    secrets: NETLIFY_SECRETS,

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

      warnings.push({
        code: CLI_ERROR_CODES.DEPLOY_EDGE_RUNTIME_WARNING,
        message:
          "Netlify target uses the Node Functions runtime. Edge Functions (Deno) are scheduled for Phase 15.3.",
        hint: "SSR will work on Node Functions; migrate to Edge later for lower latency.",
      });

      if (options.execute) {
        const status = await getProviderCliStatus("netlify", MIN_NETLIFY_VERSION, {
          spawnImpl: internal.spawnImpl,
        });
        if (!status.installed) {
          errors.push({
            code: CLI_ERROR_CODES.DEPLOY_PROVIDER_CLI_MISSING,
            message: "netlify CLI is not installed.",
            hint: "Install with `bun add -g netlify-cli` or `npm i -g netlify-cli`.",
          });
        } else if (!status.meetsMinimum && status.version) {
          errors.push({
            code: CLI_ERROR_CODES.DEPLOY_PROVIDER_CLI_OUTDATED,
            message: `netlify ${status.version} is older than required ${MIN_NETLIFY_VERSION}.`,
            hint: "Upgrade with `bun add -g netlify-cli@latest`.",
          });
        }
      }

      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 netlifyTomlResult = await writeArtifact({
        forbiddenValues: options.forbiddenSecrets,
        path: path.join(rootDir, "netlify.toml"),
        content: renderNetlifyToml({ projectName }),
        preserveIfExists: true,
      });
      artifacts.push({
        path: netlifyTomlResult.path,
        preserved: netlifyTomlResult.preserved,
        description: netlifyTomlResult.preserved
          ? "Existing netlify.toml preserved"
          : `Scaffolded netlify.toml (project=${projectName})`,
      });

      const ssrResult = await writeArtifact({
        forbiddenValues: options.forbiddenSecrets,
        path: path.join(rootDir, "netlify", "functions", "ssr.ts"),
        content: renderNetlifySsrFunction(),
      });
      artifacts.push({
        path: ssrResult.path,
        description: "Netlify Node Functions SSR entry",
      });

      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: "netlify CLI spawn harness is provided only via DeployAdapter.deployImpl.",
            hint: "Run `netlify deploy --prod` manually after `mandu deploy --target=netlify`.",
          },
        ],
      };
    },
  };
}

export const netlifyAdapter: DeployAdapter = createNetlifyAdapter();
