import fs from 'node:fs';
import { z } from 'zod';
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { config } from '../../config.js';
import { resolveWorkspacePath } from '../../lib/sandbox.js';
import { runCommand } from '../../lib/runCommand.js';
import { errorResult, jsonResult, targetSchema, toErrorMessage } from '../shared.js';

/**
 * Deploy is deliberately opt-in and project-defined: rather than guessing at
 * Vercel/Netlify/Forge credentials, it runs a script the project itself
 * defines (deploy.ps1 on Windows, deploy.sh elsewhere) at the workspace
 * root. Optionally pushes a Docker image when registryPush is set.
 */
export function registerDeployProjectTool(server: McpServer): void {
  server.registerTool(
    'deploy_project',
    {
      title: 'Deploy Project',
      description:
        'Run the project-defined deploy step: deploy.ps1 (Windows) or deploy.sh (macOS/Linux) at the workspace root. ' +
        'Requires confirm: true. Optional registryPush runs `docker push` after the deploy script (also confirm-gated). ' +
        'The server never holds deployment credentials itself.',
      inputSchema: {
        target: targetSchema,
        confirm: z
          .boolean()
          .optional()
          .describe('Must be true; deliberate opt-in for a destructive/external-effect operation.'),
        registryPush: z
          .string()
          .optional()
          .describe('Optional Docker image reference to push after the deploy script succeeds.'),
      },
    },
    async ({ confirm, target, registryPush }) => {
      void target;
      if (confirm !== true) return errorResult('confirm: true is required to run deploy_project.');

      try {
        let deployResult: Record<string, unknown>;

        if (process.platform === 'win32') {
          const scriptPath = resolveWorkspacePath(config.workspaceRoot, 'deploy.ps1');
          if (!fs.existsSync(scriptPath)) {
            return errorResult(
              `No deploy.ps1 found at the workspace root (${config.workspaceRoot}). Add one that performs your deployment, then retry.`,
            );
          }
          const result = await runCommand(
            'powershell',
            ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', scriptPath],
            { cwd: config.workspaceRoot, timeoutMs: config.scaffoldCommandTimeoutMs },
          );
          deployResult = {
            ranScript: 'deploy.ps1',
            exitCode: result.exitCode,
            timedOut: result.timedOut,
            failed: result.failed,
            output: result.output,
            cwd: result.cwd,
          };
          if (result.failed) return jsonResult({ ...deployResult, registryPush: null });
        } else {
          const scriptPath = resolveWorkspacePath(config.workspaceRoot, 'deploy.sh');
          if (!fs.existsSync(scriptPath)) {
            return errorResult(
              `No deploy.sh found at the workspace root (${config.workspaceRoot}). Add one that performs your deployment, then retry.`,
            );
          }
          const result = await runCommand('sh', [scriptPath], {
            cwd: config.workspaceRoot,
            timeoutMs: config.scaffoldCommandTimeoutMs,
          });
          deployResult = {
            ranScript: 'deploy.sh',
            exitCode: result.exitCode,
            timedOut: result.timedOut,
            failed: result.failed,
            output: result.output,
            cwd: result.cwd,
          };
          if (result.failed) return jsonResult({ ...deployResult, registryPush: null });
        }

        let pushResult: Record<string, unknown> | null = null;
        if (registryPush) {
          if (registryPush.includes('--force') || /\s/.test(registryPush)) {
            return errorResult('Invalid registryPush image reference.');
          }
          const pushed = await runCommand('docker', ['push', registryPush], {
            cwd: config.workspaceRoot,
            timeoutMs: config.scaffoldCommandTimeoutMs,
          });
          pushResult = {
            image: registryPush,
            exitCode: pushed.exitCode,
            failed: pushed.failed,
            timedOut: pushed.timedOut,
            output: pushed.output,
          };
        }

        return jsonResult({ ...deployResult, registryPush: pushResult });
      } catch (error) {
        return errorResult(toErrorMessage(error));
      }
    },
  );
}
