import * as path from 'path';
import { existsSync } from 'fs';
import { execSync } from 'child_process';
import { mkdir, readdir, stat, cp, copyFile, readFile, writeFile } from '@frontmcp/utils';
import type { AdapterTemplate } from '../types';

type PackageManager = 'npm' | 'yarn' | 'pnpm' | 'bun';

interface PackageManagerConfig {
  install: string;
  run: string;
}

const PACKAGE_MANAGERS: Record<PackageManager, PackageManagerConfig> = {
  bun: { install: 'bun install', run: 'bun run build' },
  pnpm: { install: 'pnpm install', run: 'pnpm run build' },
  yarn: { install: 'yarn install', run: 'yarn build' },
  npm: { install: 'npm install', run: 'npm run build' },
};

const LOCKFILE_TO_PM: Record<string, PackageManager> = {
  'bun.lockb': 'bun',
  'pnpm-lock.yaml': 'pnpm',
  'yarn.lock': 'yarn',
  'package-lock.json': 'npm',
};

/**
 * Detect package manager based on lockfile presence.
 * Priority: bun > pnpm > yarn > npm (fastest to slowest install times)
 */
function detectPackageManager(cwd: string): PackageManager {
  for (const [lockfile, pm] of Object.entries(LOCKFILE_TO_PM)) {
    if (existsSync(path.join(cwd, lockfile))) {
      return pm;
    }
  }
  return 'npm'; // Default fallback
}

/**
 * Vercel adapter - serverless deployment on Vercel.
 * Compiles to ESM, bundles with rspack to CJS for maximum compatibility.
 *
 * Uses Vercel Build Output API for deployment:
 * - Creates .vercel/output/config.json with routing
 * - Creates .vercel/output/functions/index.func/ with handler
 *
 * The build process:
 * 1. TypeScript compiles to ESM in dist/
 * 2. serverless-setup.js is generated (sets FRONTMCP_SERVERLESS=1)
 * 3. index.js imports setup first, then main module
 * 4. rspack bundles everything into handler.cjs
 * 5. Build Output API structure is created in .vercel/output/
 *
 * @see https://vercel.com/docs/build-output-api/v3
 */
export const vercelAdapter: AdapterTemplate = {
  // #368 round-3 — was 'esnext'. The ESM entry forced rspack to treat the
  // bundle as strict ESM, which requires fully-specified `./foo.js` imports
  // in TS-emitted code. The `byDependency.fullySpecified: false` workaround
  // didn't reliably suppress this. Switching to CJS mirrors the cloudflare
  // adapter (which has been stable) and eliminates the entire class of
  // extensionless-import errors. The bundled CJS handler still works on
  // Vercel's Node runtime since rspack outputs CommonJS.
  moduleFormat: 'commonjs',
  shouldBundle: true,
  bundleOutput: 'handler.cjs',

  getSetupTemplate: () => `// Serverless environment setup - MUST be required first
// This sets FRONTMCP_SERVERLESS before any decorators run
process.env.FRONTMCP_SERVERLESS = '1';
process.env.FRONTMCP_DEPLOYMENT_MODE = 'serverless';
`,

  getEntryTemplate: (mainModulePath: string) => `// Auto-generated Vercel entry point
// Generated by: frontmcp build --target vercel
require('./serverless-setup.js');
require('${mainModulePath}');
const { getServerlessHandlerAsync } = require('@frontmcp/sdk');

let handlerPromise = null;

module.exports = async function handler(req, res) {
  if (!handlerPromise) {
    handlerPromise = getServerlessHandlerAsync();
  }
  const app = await handlerPromise;
  return app(req, res);
};
module.exports.default = module.exports;
`,

  // Detect package manager and generate appropriate vercel.json
  getConfig: (cwd: string) => {
    const pm = detectPackageManager(cwd);
    const config = PACKAGE_MANAGERS[pm];
    return {
      version: 2,
      buildCommand: config.run,
      installCommand: config.install,
    };
  },

  configFileName: 'vercel.json',

  /**
   * Create Vercel Build Output API structure after bundling.
   * This allows Vercel to deploy the function without needing an /api folder.
   *
   * Structure created:
   * .vercel/output/
   * ├── config.json          (routes all requests to index function)
   * └── functions/
   *     └── index.func/
   *         ├── .vc-config.json  (Node.js 22 runtime config)
   *         ├── handler.cjs      (bundled handler + chunks)
   *         └── node_modules/    (runtime dependencies that can't be bundled)
   */
  postBundle: async (outDir: string, cwd: string, bundleOutput: string) => {
    const outputDir = path.join(cwd, '.vercel', 'output');
    const funcDir = path.join(outputDir, 'functions', 'index.func');

    // Create directories
    await mkdir(funcDir, { recursive: true });

    // Copy all files from dist to the function directory
    // This includes handler.cjs and any chunk files (*.handler.cjs)
    const distFiles = await readdir(outDir);
    for (const file of distFiles) {
      const srcPath = path.join(outDir, file);
      const destPath = path.join(funcDir, file);
      const fileStat = await stat(srcPath);

      if (fileStat.isDirectory()) {
        // Recursively copy directories
        await cp(srcPath, destPath, { recursive: true });
      } else {
        // Copy files
        await copyFile(srcPath, destPath);
      }
    }

    // Install runtime dependencies that can't be statically bundled (dynamic requires)
    // These are packages loaded via require() inside functions that rspack can't analyze
    // We install them fresh to ensure correct platform binaries (linux-x64 for Vercel).
    //
    // #368 round-3 — `openai` and `@anthropic-ai/sdk` are peer deps consumed
    // by `@Agent({ adapter: 'openai' | 'anthropic' })` via lazy
    // `await import()` inside the SDK. They're externalized at bundle time
    // so rspack doesn't fail when they're not installed; only ship them on
    // Vercel when the user actually has them in their package.json (i.e.,
    // they wired up an agent). If absent, the lazy require never fires.
    const runtimeDeps = ['@vercel/kv', 'esbuild', '@swc/core', 'openai', '@anthropic-ai/sdk'];

    // Read package.json to get the exact versions
    const pkgJsonPath = path.join(cwd, 'package.json');
    const pkgJson = JSON.parse(await readFile(pkgJsonPath, 'utf-8'));
    const allDeps = { ...pkgJson.dependencies, ...pkgJson.devDependencies };

    // Build list of deps with versions
    const depsToInstall: string[] = [];
    for (const dep of runtimeDeps) {
      const version = allDeps[dep];
      if (version) {
        depsToInstall.push(`${dep}@${version}`);
      }
    }

    if (depsToInstall.length > 0) {
      // Create package.json in function directory
      const funcPkgJson = { name: 'index.func', private: true, dependencies: {} };
      await writeFile(path.join(funcDir, 'package.json'), JSON.stringify(funcPkgJson, null, 2));

      // Install dependencies using npm (works on all platforms)
      try {
        execSync(`npm install ${depsToInstall.join(' ')} --omit=dev`, {
          cwd: funcDir,
          stdio: 'pipe',
        });
      } catch {
        // Silently continue if install fails - the dep might not be needed
      }
    }

    // Create function config (.vc-config.json)
    const vcConfig = {
      runtime: 'nodejs24.x',
      handler: bundleOutput,
      launcherType: 'Nodejs',
    };
    await writeFile(
      path.join(funcDir, '.vc-config.json'),
      JSON.stringify(vcConfig, null, 2),
    );

    // Create output config (config.json) with routing
    const outputConfig = {
      version: 3,
      routes: [{ src: '/(.*)', dest: '/index' }],
    };
    await writeFile(
      path.join(outputDir, 'config.json'),
      JSON.stringify(outputConfig, null, 2),
    );
  },
};
