#!/usr/bin/env node
/**
 * AI Stack Deployer MCP Server
 *
 * Exposes deployment functionality through the Model Context Protocol,
 * allowing Claude Code and other MCP clients to deploy and manage AI stacks.
 */

import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
  CallToolRequestSchema,
  ListToolsRequestSchema,
  Tool,
} from '@modelcontextprotocol/sdk/types.js';
import { createDokployClient } from './api/dokploy.js';

// Deployment state tracking
interface DeploymentState {
  id: string;
  name: string;
  status: 'initializing' | 'creating_project' | 'creating_application' | 'deploying' | 'completed' | 'failed';
  url?: string;
  error?: string;
  createdAt: Date;
  projectId?: string;
  applicationId?: string;
}

const deployments = new Map<string, DeploymentState>();

// Generate a unique deployment ID
function generateDeploymentId(): string {
  return `dep_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
}

// Validate stack name
function validateStackName(name: string): { valid: boolean; error?: string } {
  if (!name || typeof name !== 'string') {
    return { valid: false, error: 'Name is required' };
  }

  const trimmedName = name.trim().toLowerCase();

  if (trimmedName.length < 3 || trimmedName.length > 20) {
    return { valid: false, error: 'Name must be between 3 and 20 characters' };
  }

  if (!/^[a-z0-9-]+$/.test(trimmedName)) {
    return { valid: false, error: 'Name can only contain lowercase letters, numbers, and hyphens' };
  }

  if (trimmedName.startsWith('-') || trimmedName.endsWith('-')) {
    return { valid: false, error: 'Name cannot start or end with a hyphen' };
  }

  const reservedNames = (process.env.RESERVED_NAMES || 'admin,api,www,root,system,test,demo,portal').split(',');
  if (reservedNames.includes(trimmedName)) {
    return { valid: false, error: `Name "${trimmedName}" is reserved` };
  }

  return { valid: true };
}

// Main deployment orchestration
async function deployStack(name: string): Promise<DeploymentState> {
  const validation = validateStackName(name);
  if (!validation.valid) {
    throw new Error(validation.error);
  }

  const normalizedName = name.trim().toLowerCase();
  const deploymentId = generateDeploymentId();

  const deployment: DeploymentState = {
    id: deploymentId,
    name: normalizedName,
    status: 'initializing',
    createdAt: new Date(),
  };

  deployments.set(deploymentId, deployment);

  try {
    // Initialize Dokploy client
    const dokployClient = createDokployClient();

    const domain = `${normalizedName}.${process.env.STACK_DOMAIN_SUFFIX || 'ai.flexinit.nl'}`;

    // Step 1: Create Dokploy project
    deployment.status = 'creating_project';
    deployments.set(deploymentId, { ...deployment });

    const projectName = `ai-stack-${normalizedName}`;
    let project = await dokployClient.findProjectByName(projectName);

    if (!project) {
      project = await dokployClient.createProject(
        projectName,
        `AI Stack for ${normalizedName}`
      );
    }

    deployment.projectId = project.projectId;
    deployments.set(deploymentId, { ...deployment });

    // Step 2: Create application
    deployment.status = 'creating_application';
    deployments.set(deploymentId, { ...deployment });

    const dockerImage = process.env.STACK_IMAGE || 'git.app.flexinit.nl/flexinit/agent-stack:latest';
    const application = await dokployClient.createApplication(
      `opencode-${normalizedName}`,
      project.projectId,
      dockerImage
    );

    deployment.applicationId = application.applicationId;
    deployments.set(deploymentId, { ...deployment });

    // Step 3: Configure domain
    await dokployClient.createDomain(
      domain,
      application.applicationId,
      true,
      8080
    );

    // Step 4: Deploy application
    deployment.status = 'deploying';
    deployments.set(deploymentId, { ...deployment });

    await dokployClient.deployApplication(application.applicationId);

    // Mark as completed
    deployment.status = 'completed';
    deployment.url = `https://${domain}`;
    deployments.set(deploymentId, { ...deployment });

    return deployment;

  } catch (error) {
    deployment.status = 'failed';
    deployment.error = error instanceof Error ? error.message : 'Unknown error';
    deployments.set(deploymentId, { ...deployment });
    throw error;
  }
}

// MCP Server setup
const server = new Server(
  {
    name: 'ai-stack-deployer',
    version: '0.1.0',
  },
  {
    capabilities: {
      tools: {},
    },
  }
);

// Define available tools
const tools: Tool[] = [
  {
    name: 'deploy_stack',
    description: 'Deploy a new AI coding assistant stack for a user. Creates Dokploy project and application, configures domain (leverages pre-configured wildcard DNS and SSL), and deploys the OpenCode server.',
    inputSchema: {
      type: 'object',
      properties: {
        name: {
          type: 'string',
          description: 'Username for the stack (3-20 chars, lowercase alphanumeric and hyphens only)',
        },
      },
      required: ['name'],
    },
  },
  {
    name: 'check_deployment_status',
    description: 'Check the status of a deployment by its ID',
    inputSchema: {
      type: 'object',
      properties: {
        deploymentId: {
          type: 'string',
          description: 'The deployment ID returned from deploy_stack',
        },
      },
      required: ['deploymentId'],
    },
  },
  {
    name: 'list_deployments',
    description: 'List all recent deployments and their statuses',
    inputSchema: {
      type: 'object',
      properties: {},
    },
  },
  {
    name: 'check_name_availability',
    description: 'Check if a stack name is available and valid',
    inputSchema: {
      type: 'object',
      properties: {
        name: {
          type: 'string',
          description: 'The name to check',
        },
      },
      required: ['name'],
    },
  },
  {
    name: 'test_api_connections',
    description: 'Test connection to Dokploy API',
    inputSchema: {
      type: 'object',
      properties: {},
    },
  },
];

// Handle list_tools request
server.setRequestHandler(ListToolsRequestSchema, async () => {
  return { tools };
});

// Handle call_tool request
server.setRequestHandler(CallToolRequestSchema, async (request) => {
  const { name, arguments: args } = request.params;

  try {
    switch (name) {
      case 'deploy_stack': {
        const { name: stackName } = args as { name: string };
        const deployment = await deployStack(stackName);

        return {
          content: [
            {
              type: 'text',
              text: JSON.stringify({
                success: true,
                deploymentId: deployment.id,
                name: deployment.name,
                status: deployment.status,
                url: deployment.url,
                message: deployment.status === 'completed'
                  ? `Stack successfully deployed at ${deployment.url}`
                  : `Deployment in progress (status: ${deployment.status})`,
              }, null, 2),
            },
          ],
        };
      }

      case 'check_deployment_status': {
        const { deploymentId } = args as { deploymentId: string };
        const deployment = deployments.get(deploymentId);

        if (!deployment) {
          return {
            content: [
              {
                type: 'text',
                text: JSON.stringify({
                  success: false,
                  error: 'Deployment not found',
                }, null, 2),
              },
            ],
          };
        }

        return {
          content: [
            {
              type: 'text',
              text: JSON.stringify({
                success: true,
                deployment: {
                  id: deployment.id,
                  name: deployment.name,
                  status: deployment.status,
                  url: deployment.url,
                  error: deployment.error,
                  createdAt: deployment.createdAt,
                },
              }, null, 2),
            },
          ],
        };
      }

      case 'list_deployments': {
        const allDeployments = Array.from(deployments.values()).map(d => ({
          id: d.id,
          name: d.name,
          status: d.status,
          url: d.url,
          error: d.error,
          createdAt: d.createdAt,
        }));

        return {
          content: [
            {
              type: 'text',
              text: JSON.stringify({
                success: true,
                deployments: allDeployments,
                total: allDeployments.length,
              }, null, 2),
            },
          ],
        };
      }

      case 'check_name_availability': {
        const { name: stackName } = args as { name: string };
        const validation = validateStackName(stackName);

        if (!validation.valid) {
          return {
            content: [
              {
                type: 'text',
                text: JSON.stringify({
                  available: false,
                  valid: false,
                  error: validation.error,
                }, null, 2),
              },
            ],
          };
        }

        const normalizedName = stackName.trim().toLowerCase();
        const dokployClient = createDokployClient();
        const projectName = `ai-stack-${normalizedName}`;
        const existingProject = await dokployClient.findProjectByName(projectName);

        return {
          content: [
            {
              type: 'text',
              text: JSON.stringify({
                available: !existingProject,
                valid: true,
                name: normalizedName,
              }, null, 2),
            },
          ],
        };
      }

      case 'test_api_connections': {
        const dokployClient = createDokployClient();
        const dokployTest = await dokployClient.testConnection();

        return {
          content: [
            {
              type: 'text',
              text: JSON.stringify({
                dokploy: dokployTest,
                overall: dokployTest.success,
                note: 'Hetzner DNS is pre-configured with wildcard - no per-deployment DNS needed'
              }, null, 2),
            },
          ],
        };
      }

      default:
        throw new Error(`Unknown tool: ${name}`);
    }
  } catch (error) {
    return {
      content: [
        {
          type: 'text',
          text: JSON.stringify({
            success: false,
            error: error instanceof Error ? error.message : 'Unknown error',
          }, null, 2),
        },
      ],
      isError: true,
    };
  }
});

// Start the server
async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.error('AI Stack Deployer MCP Server running on stdio');
}

main().catch((error) => {
  console.error('Fatal error:', error);
  process.exit(1);
});
