/*
 * 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 { execCommand, execCommandWithOsascript } from './commandExecutor';
import { TunnelInfo } from './tunnel/tunnelInfo';
import { TunnelProtocol } from './tunnel/tunnelProtocol';

export interface DebugServerConnectInfo {
  address: string;
  port: number;
  connectCommand: string;
}

/**
 * Utility for interacting with `pymobiledevice3`
 */
export class PyMobileDevice3 {

  private static readonly IOS_TUNNEL_ADMIN_PROMPT =
    'Administrator privileges are required to start the iOS tunnel.';

  private static readonly START_TUNNEL_COMMAND = 'pymobiledevice3 lockdown start-tunnel';

  private static readonly START_DEBUG_SERVER_COMMAND = 'pymobiledevice3 developer debugserver start-server';

  public static pythonPath = 'python3';

  /**
   * Starts a `pymobiledevice3` tunnel
   *
   * @returns Tunnel information
   */
  public static async startTunnel(): Promise<TunnelInfo> {
    let pyCommand = `${this.pythonPath} -m ${this.START_TUNNEL_COMMAND}`;
    let result = await execCommandWithOsascript(
      pyCommand,
      '--rsd',
      this.IOS_TUNNEL_ADMIN_PROMPT);
    let tunnelInfo = this.parseTunnelOutput(result.output);
    tunnelInfo.pid = result.pid;
    return tunnelInfo;
  }

  /**
   * Starts a debug server through the tunnel
   *
   * @param tunnelInfo Tunnel connection information
   * @returns Debug server connection details
   */
  public static async startDebugServer(tunnelInfo: TunnelInfo): Promise<DebugServerConnectInfo> {
    let command = `${this.START_DEBUG_SERVER_COMMAND} ${tunnelInfo.rsdOption}`;
    let pyCommand = `${this.pythonPath} -m ${command}`;
    let result = await execCommand(pyCommand);
    return this.parseDebugServerOutput(result.output);
  }

  /**
   * Parses debug server output to extract connection information
   *
   * @param output Command output text
   * @returns Debug server connection details
   */
  public static async parseDebugServerOutput(output: string): Promise<DebugServerConnectInfo> {
    if (!output || output.trim().length === 0) {
      throw new Error('Failed to start debugserver');
    }
    let match = output.match(/connect:\/\/\[(.+?)\]:(\d+)/);

    if (!match) {
      throw new Error(`Failed to parse debugserver connection info from output.\nOutput:\n${output}`);
    }
    let address = match[1];
    let port = Number(match[2]);

    if (Number.isNaN(port)) {
      throw new Error(`invalid port parsed from debugserver output: ${match[0]}`);
    }
    return { address, port, connectCommand: `connect://[${address}]:${port}` };
  }

  /**
   * Parses tunnel command output
   *
   * @param output Command output text
   * @returns Tunnel information
   */
  public static parseTunnelOutput(output: string): TunnelInfo {
    let info: TunnelInfo = {
      pid: 0,
      identifier: '',
    };
    let lines = output.split(/\r?\n/);

    let kvHandlers: Record<string, (v: string) => void> = {
      'Identifier': v => info.identifier = v,
      'Interface': v => info.interface = v,
      'Protocol': v => info.protocol = v.split('.')[1] === 'TCP' ? TunnelProtocol.TCP : TunnelProtocol.QUIC,
      'RSD Address': v => info.rsdAddress = v,
      'RSD Port': v => info.rsdPort = Number(v),
    };
    let expectRsdOption = false;
    for (let rawLine of lines) {
      let line = rawLine.trim();
      if (!line) {
        continue;
      }
      let sepIndex = line.indexOf(':');
      if (sepIndex !== -1) {
        let key = line.slice(0, sepIndex).trim();
        let value = line.slice(sepIndex + 1).trim();
        let handler = kvHandlers[key];
        if (handler) {
          handler(value);
          continue;
        }
        if (key === 'Use the follow connection option') {
          expectRsdOption = true;
          continue;
        }
      }
      if (expectRsdOption && line.startsWith('--rsd ')) {
        info.rsdOption = line;
        expectRsdOption = false;
      }
      if (!info.identifier ||
        !info.interface ||
        !info.rsdAddress ||
        !info.rsdPort ||
        !info.rsdOption) {
        console.log('Failed to parse tunnel creation output line: ', line);
      }
    }
    return info;
  }
}