import {errors} from 'appium/driver';
import path from 'node:path';
import {fs, tempDir} from '@appium/support';
import crypto from 'node:crypto';
import type {AndroidDriver} from '../driver';

const EMULATOR_RESOURCES_ROOT = ['emulator', 'resources'];
const CONFIG_NAME = 'Toren1BD.posters';
const DEFAULT_CONFIG_PAYLOAD_PREFIX = `poster wall
size 2 2
position -0.807 0.320 5.316
rotation 0 -150 0
default poster.png

poster table`;
const DEFAULT_CONFIG_PAYLOAD = `${DEFAULT_CONFIG_PAYLOAD_PREFIX}
size 1 1
position 0 0 -1.5
rotation 0 0 0
`;
const PNG_MAGIC = '89504e47';
const PNG_MAGIC_LENGTH = 4;

/**
 * Updates the emulator configuration to show the
 * image using the given properties when one opens the Camera app.
 *
 * @param sdkRoot ADB SDK root folder path
 * @returns Promise that resolves to `true` if emulator services restart is needed
 * to load the updated config or `false` if the current config is already up to date
 * or does not need to be updated
 */
export async function prepareEmulatorForImageInjection(
  this: AndroidDriver,
  sdkRoot: string,
): Promise<boolean> {
  const {injectedImageProperties: props} = this.opts;
  if (!props) {
    return false;
  }

  const size = `size ${props.size?.scaleX ?? 1} ${props.size?.scaleY ?? 1}`;
  const position = `position ${props.position?.x ?? 0} ${props.position?.y ?? 0} ${props.position?.z ?? -1.5}`;
  const rotation = `rotation ${props.rotation?.x ?? 0} ${props.rotation?.y ?? 0} ${props.rotation?.z ?? 0}`;
  const strProps = `${size}\n${position}\n${rotation}`;
  const configPath = path.resolve(sdkRoot, ...EMULATOR_RESOURCES_ROOT, CONFIG_NAME);
  if (await fs.exists(configPath)) {
    const configPayload = await fs.readFile(configPath, 'utf8');
    if (configPayload.includes(strProps)) {
      this.log.info(
        `The image injection config at '${configPath}' is already up to date. Doing nothing`,
      );
      return false;
    }
    const updatedPayload = `${DEFAULT_CONFIG_PAYLOAD_PREFIX}
    ${size}
    ${position}
    ${rotation}
    `;
    await fs.writeFile(configPath, updatedPayload, 'utf-8');
  } else {
    await fs.writeFile(configPath, DEFAULT_CONFIG_PAYLOAD, 'utf-8');
  }
  this.log.info(
    `The image injection config at '${configPath}' has been successfully updated with ` +
      `${size}, ${position}, ${rotation}. ` +
      `Expecting further emulator restart to reload the changed config.`,
  );
  return true;
}

/**
 * Updates the emulator configuration to show the given
 * image on the foreground when one opens the Camera app.
 * It is expected that the rear camera of the emulator is
 * configured to show VirtualScene for this feature to work.
 * It is expected that the Virtual Scene posters config is
 * already prepared and loaded either manually or using the
 * `injectedImageProperties` capability.
 *
 * @param payload Base64-encoded payload of a .png image to be injected
 * @returns Promise that resolves when the image is injected.
 * @throws {Error} If called on a non-emulator device.
 * @throws {errors.InvalidArgumentError} If the payload is not a valid base64-encoded PNG.
 */
export async function mobileInjectEmulatorCameraImage(
  this: AndroidDriver,
  payload: string,
): Promise<void> {
  if (!this.isEmulator()) {
    throw new Error('The image injection feature is only available on emulators');
  }

  if (typeof payload !== 'string' || payload.length <= PNG_MAGIC_LENGTH) {
    throw new errors.InvalidArgumentError(
      `You must provide a valid base64-encoded .PNG data as the 'payload' argument`,
    );
  }
  const pngBuffer = Buffer.from(payload, 'base64');
  const magic = pngBuffer.toString('hex', 0, PNG_MAGIC_LENGTH);
  if (magic !== PNG_MAGIC) {
    throw new errors.InvalidArgumentError(
      'The provided image payload must contain a valid base64-encoded .PNG data',
    );
  }

  const tmpImagePath = await tempDir.path({
    // this is needed to avoid creation of multiple temp files for the same image payload
    prefix: calculateImageHash(pngBuffer),
    suffix: '.png',
  });
  await fs.writeFile(tmpImagePath, pngBuffer);
  await this.adb.execEmuConsoleCommand(`virtualscene-image table ${tmpImagePath}`);
  this.log.info(
    `The provided image has been successully injected to the ${this.adb.curDeviceId} emulator`,
  );
}

// #region Internal helpers

function calculateImageHash(buffer: Buffer): string {
  const hasher = crypto.createHash('sha1');
  hasher.update(buffer);
  return hasher.digest('hex');
}

// #endregion
