/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import { exec } from 'child_process';

/**
 * Result of executing a command via osascript
 */
export interface OsascriptExecResult {
  pid: number;
  output: string;
}

/**
 * Result of executing a shell command
 */
export interface ExecResult {
  stdout: string;
  stderr: string;
  output: string;
}

/**
 * Executes a shell command
 *
 * @param command Command to execute
 * @param timeout Command timeout in milliseconds. Default is 10000
 * @returns The execution result
 */
export function execCommand(command: string, timeout: number = 10000): Promise<ExecResult> {
  return new Promise((resolve, reject) => {
    exec(command, { timeout }, (error, stdout, stderr) => {
      let out = stdout ?? '';
      let err = stderr ?? '';
      let output = out + err;
      if (error) {
        reject(new Error(
          `Command execution failed.\n` +
          `Command: ${command}\n` +
          `Error: ${error.message}\n` +
          `Output:\n${output}`,
        ));
        return;
      }
      resolve({
        stdout: out,
        stderr: err,
        output
      });
    });
  });
}

/**
 * Executes a command via osascript (typically for commands requiring privileges)
 *
 * @param command Command to execute
 * @param readFlag Flag used to detect when the command is ready
 * @param prompt Optional prompt message shown in the password dialog
 * @param timeout Command timeout in milliseconds. Default is 10000
 * @returns The execution result
 */
export async function execCommandWithOsascript(command: string, readFlag: string, prompt: string = '',
                                               timeout: number = 10000): Promise<OsascriptExecResult> {
  let tmpDir = os.tmpdir();
  let logFile = path.join(tmpDir, `cjmp_debug_tunnel_${Date.now()}.log`);
  let pidFile = path.join(tmpDir, `cjmp_debug_tunnel_${Date.now()}.pid`);
  fs.writeFileSync(logFile, '');
  let shellCmd = `${command} > "${logFile}" 2>&1 & echo $! > "${pidFile}"`;
  let osaCommand = `osascript -e 'do shell script "${shellCmd.replace(/\n/g, ' ').replace(/"/g, '\\"')}" with administrator privileges with prompt "${prompt}"'`;
  exec(osaCommand);
  return new Promise((resolve, reject) => {
    let startTime = Date.now();
    let timer = setInterval(() => {
      try {
        if (!fs.existsSync(pidFile)) {
          return;
        }
        let pid = Number(fs.readFileSync(pidFile, 'utf8').trim());
        if (!pid) {
          return;
        }
        let content = fs.readFileSync(logFile, 'utf8');
        if (content.includes(readFlag)) {
          clearInterval(timer);
          fs.unlinkSync(logFile);
          fs.unlinkSync(pidFile);
          resolve({
            pid,
            output: content,
          });
        }
        if (Date.now() - startTime > timeout) {
          clearInterval(timer);
          reject(new Error('Tunnel start timeout.'));
        }
      } catch (err) {
        // do nothing
      }
    }, 100);
  });
}