/**
 * Ansible Runner
 * Handles remote server provisioning via SSH
 */
import { SSHClient, SSHStreamEvent } from '../ssh'
import { SSH_PORT_BEFORE_PROVISION } from '../ssh/constants'
import {
  AnsibleConfig,
  AnsibleResult,
  AnsibleLogEvent,
  AnsibleLogCallback,
  PROVISIONING_STAGES,
} from './types'

const REMOTE_SCRIPTS_PATH = '/opt/letsbe-setup'
const DEFAULT_USERNAME = 'root'
const SETUP_TIMEOUT = 30 * 60 * 1000 // 30 minutes

/**
 * Generate the setup configuration JSON for the target server
 */
function generateSetupConfig(config: AnsibleConfig): string {
  return JSON.stringify(
    {
      domain: config.domain,
      admin_email: config.adminEmail || `admin@${config.domain}`,
      tools: config.tools,
      hub: {
        url: config.hubUrl,
        registration_token: config.registrationToken,
      },
      versions: {
        orchestrator: config.orchestratorVersion || 'latest',
        agent: config.agentVersion || 'latest',
      },
    },
    null,
    2
  )
}

/**
 * Generate the bootstrap script that will be executed on the remote server
 */
function generateBootstrapScript(config: AnsibleConfig): string {
  return `#!/bin/bash
set -e

echo "=== LetsBe Server Setup ==="
echo "Domain: ${config.domain}"
echo "Hub URL: ${config.hubUrl}"
echo ""

# Create base directories
mkdir -p /opt/letsbe/{env,stacks,nginx,scripts,config}
mkdir -p ${REMOTE_SCRIPTS_PATH}

# Update system
echo "[STAGE:bootstrap] Updating system packages..."
apt-get update -qq
apt-get upgrade -y -qq

# Install prerequisites
echo "[STAGE:bootstrap] Installing prerequisites..."
apt-get install -y -qq curl wget git jq

# Install Docker if not present
if ! command -v docker &> /dev/null; then
  echo "[STAGE:docker] Installing Docker..."
  curl -fsSL https://get.docker.com | sh
  systemctl enable docker
  systemctl start docker
else
  echo "[STAGE:docker] Docker already installed"
fi

# Install Docker Compose plugin if not present
if ! docker compose version &> /dev/null; then
  echo "[STAGE:docker] Installing Docker Compose plugin..."
  apt-get install -y -qq docker-compose-plugin
else
  echo "[STAGE:docker] Docker Compose already installed"
fi

# Pull and deploy orchestrator
echo "[STAGE:orchestrator] Deploying LetsBe Orchestrator..."
cd /opt/letsbe/stacks
mkdir -p orchestrator && cd orchestrator

cat > docker-compose.yml << 'COMPOSE_EOF'
version: '3.8'
services:
  orchestrator-api:
    image: code.letsbe.solutions/letsbe/letsbe-orchestrator:latest
    container_name: letsbe-orchestrator-api
    restart: unless-stopped
    ports:
      - "8100:8100"
    environment:
      - DATABASE_URL=postgresql://postgres:postgres@orchestrator-db:5432/orchestrator
      - ADMIN_TOKEN=\${ADMIN_TOKEN}
      - HUB_URL=${config.hubUrl}
      - REGISTRATION_TOKEN=${config.registrationToken}
    depends_on:
      - orchestrator-db
    networks:
      - letsbe-network

  orchestrator-db:
    image: postgres:15-alpine
    container_name: letsbe-orchestrator-db
    restart: unless-stopped
    environment:
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=postgres
      - POSTGRES_DB=orchestrator
    volumes:
      - orchestrator-data:/var/lib/postgresql/data
    networks:
      - letsbe-network

networks:
  letsbe-network:
    name: letsbe-network
    driver: bridge

volumes:
  orchestrator-data:
COMPOSE_EOF

# Generate admin token
ADMIN_TOKEN=$(openssl rand -hex 32)
echo "ADMIN_TOKEN=\${ADMIN_TOKEN}" > .env
echo "\${ADMIN_TOKEN}" > /opt/letsbe/env/orchestrator_admin_token

docker compose pull
docker compose up -d

echo "[STAGE:orchestrator] Waiting for orchestrator to be healthy..."
sleep 10

# Deploy SysAdmin Agent
echo "[STAGE:agent] Deploying SysAdmin Agent..."
cd /opt/letsbe/stacks
mkdir -p sysadmin-agent && cd sysadmin-agent

cat > docker-compose.yml << 'AGENT_EOF'
version: '3.8'
services:
  sysadmin-agent:
    image: code.letsbe.solutions/letsbe/letsbe-sysadmin:latest
    container_name: letsbe-sysadmin-agent
    restart: unless-stopped
    environment:
      - ORCHESTRATOR_URL=http://letsbe-orchestrator-api:8100
      - AGENT_NAME=sysadmin-agent-1
    volumes:
      - /opt/letsbe:/opt/letsbe
      - /var/run/docker.sock:/var/run/docker.sock
    networks:
      - letsbe-network
AGENT_EOF

docker compose pull
docker compose up -d

echo "[STAGE:agent] Agent deployed successfully"

# Register with Hub
echo "[STAGE:register] Registering with LetsBe Hub..."
curl -X POST "${config.hubUrl}/api/v1/orchestrator/register" \\
  -H "Content-Type: application/json" \\
  -d '{
    "registrationToken": "${config.registrationToken}",
    "orchestratorUrl": "http://'$(hostname -I | awk '{print $1}')':8100",
    "agentVersion": "latest"
  }' || echo "Warning: Hub registration failed, will retry on heartbeat"

echo "[STAGE:complete] Server provisioning complete!"
echo ""
echo "=== Summary ==="
echo "Orchestrator: http://localhost:8100"
echo "Domain: ${config.domain}"
echo "Registration Token: ${config.registrationToken}"
`
}

/**
 * Parse log output to detect provisioning stages
 */
function parseLogForStage(message: string): string | null {
  const stageMatch = message.match(/\[STAGE:(\w+)\]/)
  if (stageMatch) {
    return stageMatch[1]
  }
  return null
}

/**
 * Calculate progress based on current stage
 */
function getProgressForStage(stageName: string): number {
  const stage = PROVISIONING_STAGES.find((s) => s.name === stageName)
  if (!stage) return 0
  return Math.round((stage.order / PROVISIONING_STAGES.length) * 100)
}

/**
 * Run the provisioning process on a remote server
 */
export async function runProvisioning(
  config: AnsibleConfig,
  callback: AnsibleLogCallback
): Promise<AnsibleResult> {
  const startTime = Date.now()
  const logs: AnsibleLogEvent[] = []

  const logEvent = async (event: AnsibleLogEvent) => {
    logs.push(event)
    await callback(event)
  }

  let sshClient: SSHClient | null = null

  try {
    // Stage 1: Connect to server
    await logEvent({
      type: 'status',
      step: 'connect',
      message: `Connecting to ${config.serverIp}...`,
      progress: getProgressForStage('connect'),
      timestamp: new Date(),
    })

    sshClient = new SSHClient(
      {
        host: config.serverIp,
        port: config.serverPort || SSH_PORT_BEFORE_PROVISION,
        username: config.username || DEFAULT_USERNAME,
        password: config.password,
        timeout: 30000,
      },
      { debug: false }
    )

    await sshClient.connect()

    await logEvent({
      type: 'status',
      step: 'connect',
      message: 'Connected successfully',
      progress: getProgressForStage('connect'),
      timestamp: new Date(),
    })

    // Stage 2: Upload setup scripts
    await logEvent({
      type: 'status',
      step: 'upload',
      message: 'Uploading setup scripts...',
      progress: getProgressForStage('upload'),
      timestamp: new Date(),
    })

    // Create remote directory
    await sshClient.runCommand(`mkdir -p ${REMOTE_SCRIPTS_PATH}`)

    // Upload config
    const configContent = generateSetupConfig(config)
    await sshClient.uploadContent(configContent, `${REMOTE_SCRIPTS_PATH}/config.json`)

    // Upload bootstrap script
    const bootstrapScript = generateBootstrapScript(config)
    await sshClient.uploadContent(bootstrapScript, `${REMOTE_SCRIPTS_PATH}/bootstrap.sh`)
    await sshClient.runCommand(`chmod +x ${REMOTE_SCRIPTS_PATH}/bootstrap.sh`)

    await logEvent({
      type: 'status',
      step: 'upload',
      message: 'Scripts uploaded successfully',
      progress: getProgressForStage('upload'),
      timestamp: new Date(),
    })

    // Stage 3+: Execute bootstrap script with streaming
    await logEvent({
      type: 'status',
      step: 'bootstrap',
      message: 'Starting server setup...',
      progress: getProgressForStage('bootstrap'),
      timestamp: new Date(),
    })

    let currentStage = 'bootstrap'

    const result = await sshClient.streamCommand(
      `${REMOTE_SCRIPTS_PATH}/bootstrap.sh 2>&1`,
      async (event: SSHStreamEvent) => {
        if (event.type === 'stdout' || event.type === 'stderr') {
          const lines = (event.data || '').split('\n').filter((l) => l.trim())

          for (const line of lines) {
            // Check for stage markers
            const detectedStage = parseLogForStage(line)
            if (detectedStage) {
              currentStage = detectedStage
            }

            await logEvent({
              type: event.type,
              step: currentStage,
              message: line,
              progress: getProgressForStage(currentStage),
              timestamp: event.timestamp,
            })
          }
        } else if (event.type === 'error') {
          await logEvent({
            type: 'error',
            step: currentStage,
            message: event.error || 'Unknown error',
            progress: getProgressForStage(currentStage),
            timestamp: event.timestamp,
          })
        }
      },
      { timeout: SETUP_TIMEOUT }
    )

    // Complete
    await logEvent({
      type: 'complete',
      step: 'complete',
      message: result.success
        ? 'Server provisioning completed successfully'
        : `Setup failed with exit code ${result.exitCode}`,
      progress: 100,
      timestamp: new Date(),
    })

    return {
      success: result.success,
      exitCode: result.exitCode,
      duration: Date.now() - startTime,
      logs,
    }
  } catch (error) {
    const errorMessage = error instanceof Error ? error.message : 'Unknown error'

    await logEvent({
      type: 'error',
      message: `Provisioning failed: ${errorMessage}`,
      timestamp: new Date(),
    })

    return {
      success: false,
      exitCode: -1,
      duration: Date.now() - startTime,
      logs,
      error: errorMessage,
    }
  } finally {
    if (sshClient) {
      sshClient.disconnect()
    }
  }
}

/**
 * Test SSH connection to a server
 */
export async function testServerConnection(
  serverIp: string,
  password: string,
  port = SSH_PORT_BEFORE_PROVISION,
  username = DEFAULT_USERNAME
): Promise<{ success: boolean; error?: string; latency?: number }> {
  const sshClient = new SSHClient({
    host: serverIp,
    port,
    username,
    password,
    timeout: 15000,
  })

  return sshClient.testConnection()
}
