#!/usr/bin/env node

import sftpClient from 'ssh2-sftp-client'
import {
  readFileSync,
  existsSync,
  statSync,
  readdirSync,
  unlinkSync,
  createReadStream,
  writeFileSync
} from 'fs'
import { join, dirname } from 'path'
import { fileURLToPath } from 'url'
import { SingleBar } from 'cli-progress'
import { exec, spawn } from 'child_process'
import { promisify } from 'util'
import { Command } from 'commander'
const program = new Command();
program.option('-n --env <env>', '发布环境', 'dev')
program.parse(process.argv);
const options = program.opts();

const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)
const projectRoot = join(__dirname, '..')

// 配置 测试环境 : 
const config = options.env === 'prod' ? {
  host: '60.205.166.0',
  username: 'root',
  password: 'U/N$$XBv', // 运行时输入
  remotePath: '/opt/data/airport-web/dist',
  localPath: join(projectRoot, 'dist'),
  accessLocation: 'http://airport.samsundot.com:9011'
} : {
  host: '192.168.3.221',
  username: 'root',
  password: 'root', // 运行时输入
  remotePath: '/opt/data/airport-web/dist',
  localPath: join(projectRoot, 'dist')
}

// 颜色输出
const colors = {
  reset: '\x1b[0m',
  bright: '\x1b[1m',
  red: '\x1b[31m',
  green: '\x1b[32m',
  yellow: '\x1b[33m',
  blue: '\x1b[34m',
  magenta: '\x1b[35m',
  cyan: '\x1b[36m'
}

function log (message, color = 'reset') {
  console.log(`${colors[ color ]}${message}${colors.reset}`)
}

// 获取密码
async function getPassword () {
  const readline = await import('readline')
  const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout
  })

  return new Promise(resolve => {
    rl.question('🔐 请输入服务器密码: ', password => {
      rl.close()
      resolve(password)
    })
  })
}

// 检查并构建 dist 目录
async function checkAndBuildDist () {
  if (!existsSync(config.localPath)) {
    log('⚠️  dist 目录不存在，开始自动构建...', 'yellow')

    // 检查 package.json 是否存在
    const packageJsonPath = join(projectRoot, 'package.json')
    if (!existsSync(packageJsonPath)) {
      log('❌ package.json 不存在', 'red')
      process.exit(1)
    }

    // 检查是否有 build 脚本
    const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'))
    if (!packageJson.scripts || !packageJson.scripts.build) {
      log('❌ package.json 中没有找到 build 脚本', 'red')
      process.exit(1)
    }

    // 执行构建
    log('🔨 正在执行构建命令...', 'cyan')
    const execAsync = promisify(exec)

    try {
      // 优先使用 yarn，如果没有则使用 npm
      let buildCommand = 'npm run build'
      let packageManager = 'npm'

      // 检查是否有 yarn
      try {
        await execAsync('yarn --version', { cwd: projectRoot })
        buildCommand = 'yarn build'
        packageManager = 'yarn'
        log(`使用 ${packageManager} 构建...`, 'cyan')
      } catch (yarnError) {
        log('yarn 不可用，使用 npm 构建...', 'yellow')
      }

      await execAsync(buildCommand, { cwd: projectRoot })

      // 再次检查构建结果
      if (!existsSync(config.localPath)) {
        log('❌ 构建失败，dist 目录仍未生成', 'red')
        process.exit(1)
      }

      log(`✅ 构建完成！(使用 ${packageManager})`, 'green')
    } catch (error) {
      log(`❌ 构建失败: ${error.message}`, 'red')
      process.exit(1)
    }
  } else {
    const stats = statSync(config.localPath)
    if (!stats.isDirectory()) {
      log('❌ dist 不是目录', 'red')
      process.exit(1)
    }
    log('✅ dist 目录检查通过', 'green')
  }
}

// 计算目录大小
function getDirectorySize (dirPath) {
  let totalSize = 0
  const files = readdirSync(dirPath, { withFileTypes: true })

  for (const file of files) {
    const fullPath = join(dirPath, file.name)
    if (file.isDirectory()) {
      totalSize += getDirectorySize(fullPath)
    } else {
      totalSize += statSync(fullPath).size
    }
  }

  return totalSize
}

// 格式化文件大小
function formatSize (bytes) {
  const units = [ 'B', 'KB', 'MB', 'GB' ]
  let size = bytes
  let unitIndex = 0

  while (size >= 1024 && unitIndex < units.length - 1) {
    size /= 1024
    unitIndex++
  }

  return `${size.toFixed(1)}${units[ unitIndex ]}`
}

// 创建压缩包
async function createArchive () {
  const execAsync = promisify(exec)

  const archivePath = join(projectRoot, 'dist.tar.gz')

  log('📦 正在创建压缩包...', 'yellow')
  try {
    // 使用 tar 创建压缩包，排除 macOS 扩展属性
    await execAsync(`tar --no-xattrs -czf "${archivePath}" -C "${config.localPath}" .`)

    const stats = statSync(archivePath)
    log(`✅ 压缩包创建完成，大小: ${formatSize(stats.size)}`, 'green')

    return archivePath
  } catch (error) {
    log(`❌ 创建压缩包失败: ${error.message}`, 'red')
    throw error
  }
}

// 获取用户选择
async function getUserChoice (question) {
  const readline = await import('readline')
  const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout
  })

  return new Promise(resolve => {
    rl.question(question, answer => {
      rl.close()
      resolve(answer.toLowerCase().trim())
    })
  })
}

// 主部署函数
async function deploy () {
  let archivePath = null

  try {
    log('🚀 开始 Node.js 部署...', 'cyan')

    // 检查并构建 dist 目录
    await checkAndBuildDist()

    if (!config.password) {
      // 获取密码
      config.password = await getPassword()
    }

    // 计算总大小
    const totalSize = getDirectorySize(config.localPath)
    log(`📊 总大小: ${formatSize(totalSize)}`, 'blue')

    // 检查是否已有压缩包，支持断点续传
    const existingArchivePath = join(projectRoot, 'dist.tar.gz')
    if (existsSync(existingArchivePath)) {
      log('📦 发现已存在的压缩包，跳过打包步骤', 'yellow')
      archivePath = existingArchivePath
    } else {
      // 创建压缩包
      archivePath = await createArchive()
    }

    // 连接 SFTP
    log('🔌 正在连接服务器...', 'yellow')
    const sftp = new sftpClient()
    await sftp.connect({
      host: config.host,
      username: config.username,
      password: config.password
    })
    log('✅ SFTP 连接成功', 'green')

    // 创建远程目录
    log('📁 创建远程目录...', 'yellow')
    await sftp.mkdir(config.remotePath, true) // true 表示递归创建

    // 检查服务器是否已存在压缩包
    const remoteArchivePath = '/opt/data/airport-web/dist.tar.gz'
    let needUpload = true

    try {
      await sftp.stat(remoteArchivePath)
      log('📦 服务器已存在压缩包', 'yellow')

      // 询问用户是否续传
      const choice = await getUserChoice('是否续传？(y/n): ')
      if (choice === 'y' || choice === 'yes') {
        log('📦 跳过上传步骤，使用现有压缩包', 'green')
        needUpload = false
      } else {
        log('🗑️  删除服务器压缩包，重新上传...', 'yellow')
        await sftp.unlink(remoteArchivePath)
        log('📤 开始上传新压缩包...', 'yellow')
      }
    } catch (error) {
      // 文件不存在，需要上传
      log('📤 服务器不存在压缩包，开始上传...', 'yellow')
    }

    if (needUpload) {
      // 上传压缩包
      log('📤 正在上传压缩包...', 'yellow')

      // 显示上传进度
      const fileSize = statSync(archivePath).size
      log(`📊 文件大小: ${formatSize(fileSize)}`, 'blue')

      // 创建进度条
      const progressBar = new SingleBar({
        format: '📤 上传进度 |{bar}| {percentage}% | {value}/{total} Bytes | {speed}',
        barCompleteChar: '█',
        barIncompleteChar: '░',
        hideCursor: true
      })

      progressBar.start(fileSize, 0)

      try {
        // 使用流的方式上传，获得实时进度
        const readStream = createReadStream(archivePath)
        const writeStream = await sftp.createWriteStream(remoteArchivePath)

        let uploadedBytes = 0
        const startTime = Date.now()

        // 监听数据流，实时更新进度
        readStream.on('data', chunk => {
          uploadedBytes += chunk.length
          const elapsedTime = (Date.now() - startTime) / 1000
          const speed = elapsedTime > 0 ? (uploadedBytes / elapsedTime).toFixed(2) : '0'
          progressBar.update(uploadedBytes, { speed: `${speed} B/s` })
        })

        // 处理流事件
        readStream.pipe(writeStream)

        // 等待上传完成
        await new Promise((resolve, reject) => {
          writeStream.on('close', () => {
            progressBar.stop()
            log('✅ 压缩包上传完成', 'green')
            resolve()
          })

          writeStream.on('error', err => {
            progressBar.stop()
            reject(err)
          })

          readStream.on('error', err => {
            progressBar.stop()
            reject(err)
          })
        })
      } catch (error) {
        progressBar.stop()
        throw error
      }
    }

    // 在服务器端解压
    log('📦 正在服务器端解压...', 'yellow')

    // 检测操作系统
    const isWindows = process.platform === 'win32'
    const execAsync = promisify(exec)


    // 转义密码（关键步骤！）
    function escapeForExpect (password) {
      return password
        .replace(/\\/g, "\\\\") // 转义反斜杠 \
        .replace(/\$/g, "\\$"); // 转义 $
    }

    if (isWindows) {
      // Windows 系统：使用 PowerShell 脚本（单次密码输入）
      log('🪟  Windows 系统检测到，使用 PowerShell 脚本', 'yellow')
      const runPowerShellScript = (scriptPath) => {
        return new Promise((resolve, reject) => {
          const child = spawn('powershell', [
            '-ExecutionPolicy',
            'Bypass',
            '-File',
            scriptPath
          ], {
            stdio: 'inherit'
          });

          child.on('close', (code) => {
            if (code === 0) {
              resolve();
            } else {
              reject(new Error(`PowerShell 脚本执行失败，退出码: ${code}`));
            }
          });

          child.on('error', (error) => {
            reject(new Error(`无法启动 PowerShell: ${error.message}`));
          });
        });
      };
      const powershellScript = `
        $username = "${config.username}"
        $hostname = "${config.host}"
        $remotePath = "${config.remotePath}"
        $archivePath = "${remoteArchivePath}"
        $safePath = [System.Management.Automation.WildcardPattern]::Escape($remotePath)
        $safeArchive = [System.Management.Automation.WildcardPattern]::Escape($archivePath)

        # 创建 SSH 命令
        $combinedCommand = "cd '$safePath' && tar -xzf '$safeArchive' && rm '$safeArchive' && chmod -R 755 '$safePath'"

        Write-Host "=== 部署脚本 ===" -ForegroundColor Green
        Write-Host "目标服务器: $username@$hostname" -ForegroundColor Yellow
        Write-Host "远程路径: $remotePath" -ForegroundColor Yellow
        Write-Host ""

        Write-Host "🔑 请输入 SSH 密码 密码：${config.password}" -ForegroundColor Cyan
        Write-Host ""

        try {
            # 执行合并命令
            Write-Host "🚀 正在执行部署操作..." -ForegroundColor Cyan
            ssh -o StrictHostKeyChecking=no $username@$hostname $combinedCommand
            
            Write-Host "✅ 所有操作完成！" -ForegroundColor Green
            
        } catch {
            Write-Host "❌ 执行失败: $_" -ForegroundColor Red
            Write-Host ""
            Write-Host "请手动执行以下命令：" -ForegroundColor Yellow
            Write-Host "ssh $username@$hostname \"cd '$remotePath' && tar -xzf '$archivePath' && rm '$archivePath' && chmod -R 755 '$remotePath'\"" -ForegroundColor Cyan
            exit 1
        }`
      const psScriptPath = join(projectRoot, 'temp_deploy.ps1');
      writeFileSync(psScriptPath, powershellScript);
      try {
        // 执行 PowerShell 脚本
        log('ℹ️  即将执行部署命令，请根据提示输入密码...', 'blue');
        await runPowerShellScript(psScriptPath)
        log('✅ 部署完成', 'green');
      } catch (error) {
        log('⚠️  执行失败，请手动执行以下命令：', 'yellow');
        log(`ssh ${config.username}@${config.host} "cd '${config.remotePath}' && tar -xzf '${remoteArchivePath}' && rm '${remoteArchivePath}' && chmod -R 755 '${config.remotePath}'"`, 'cyan');

        // 等待用户确认
        const readline = await import('readline');
        const rl = readline.createInterface({
          input: process.stdin,
          output: process.stdout
        });

        await new Promise(resolve => {
          rl.question('执行完成后按回车键继续...', () => {
            rl.close();
            resolve();
          });
        });
      } finally {
        if (existsSync(psScriptPath)) {
          unlinkSync(psScriptPath);
        }
      }
    } else {
      // Unix/Linux/macOS 系统：使用 expect 脚本
      const expectScript = `#!/usr/bin/expect -f
        set timeout 30
        set password {${config.password}}
        spawn ssh -o StrictHostKeyChecking=no ${config.username}@${config.host} "cd ${config.remotePath} && tar -xzf ${remoteArchivePath} && rm ${remoteArchivePath}"
        expect "password:"
        send "$password\r"
        expect eof
      `

      const expectScriptPath = join(projectRoot, 'temp_expect.sh')
      writeFileSync(expectScriptPath, expectScript)

      try {
        await execAsync(`chmod +x ${expectScriptPath}`)
        await execAsync(expectScriptPath)
        log('✅ 解压完成', 'green')
      } finally {
        if (existsSync(expectScriptPath)) {
          unlinkSync(expectScriptPath)
        }
      }

      // 设置权限
      log('🔧 设置文件权限...', 'yellow')

      const chmodExpectScript = `#!/usr/bin/expect -f
        set timeout 30
        set password {${config.password}}
        spawn ssh -o StrictHostKeyChecking=no ${config.username}@${config.host} "chmod -R 755 ${config.remotePath}"
        expect "password:"
        send "$password\r"
        expect eof
      `

      const chmodScriptPath = join(projectRoot, 'temp_chmod.sh')
      writeFileSync(chmodScriptPath, chmodExpectScript)

      try {
        await execAsync(`chmod +x ${chmodScriptPath}`)
        await execAsync(chmodScriptPath)
        log('✅ 权限设置完成', 'green')
      } finally {
        if (existsSync(chmodScriptPath)) {
          unlinkSync(chmodScriptPath)
        }
      }
    }

    // 清理服务器上的压缩包
    log('🗑️  清理服务器压缩包...', 'yellow')
    try {
      await sftp.unlink(remoteArchivePath)
      log('✅ 服务器压缩包已清理', 'green')
    } catch (error) {
      log('⚠️  服务器压缩包清理失败（可能已被删除）', 'yellow')
    }

    // 关闭连接
    await sftp.end()
    log('🗑️  清理本地压缩包...', 'yellow')
    if (archivePath && existsSync(archivePath)) {
      unlinkSync(archivePath)
    }

    log('🎉 部署成功！', 'green')
    config.accessLocation ? log(`🌐 访问地址: ${config.accessLocation}`, 'cyan') : log(`🌐 访问地址: http://${config.host}`, 'cyan')
  } catch (error) {
    log(`❌ 部署失败: ${error.message}`, 'red')
    console.error(error)
    process.exit(1)
  }
}

// 运行部署
deploy()