/*
 * Copyright (c) 2026 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 assert from 'assert';
import * as path from 'path';
import * as vscode from 'vscode';
import { DebugProtocol } from 'vscode-debugprotocol';

import { createKeelsDebugAdapterForTest, KeelsConfigurationProvider } from '../../src/keelsRun';
import { RemotePlatform } from '../../src/types';
import { Device, KeelsDeviceManager } from '../../src/utils';
import { activateCjmpExtension } from '../helpers/extensionHost';

suite('Keels debug basic protocol', () => {
  /**
   * @function testResolvesAnEmptyDebugConfigurationFromTheSelectedDevice
   * @description Tests whether an empty debug configuration can be resolved from the selected device.
   * @returns {Promise<void>} - Returns a Promise that resolves when the async test assertions have completed.
   */
  test('resolves an empty debug configuration from the selected device', async () => {
    await activateCjmpExtension();
    const provider = new KeelsConfigurationProvider(
      new StaticDeviceManager() as unknown as KeelsDeviceManager,
    );
    const document = await vscode.workspace.openTextDocument(
      vscode.Uri.joinPath(vscode.workspace.workspaceFolders![0].uri, 'lib', 'index.cj'),
    );
    await vscode.window.showTextDocument(document);

    const config = await provider.resolveDebugConfiguration(undefined, {} as vscode.DebugConfiguration);

    assert.strictEqual(config.type, 'keels');
    assert.strictEqual(config.name, 'Launch');
    assert.strictEqual(config.request, 'launch');
    assert.strictEqual(config.program, '${file}');
    assert.strictEqual(config.deviceId, 'test-android-device');
    assert.strictEqual(config.remotePlatform, RemotePlatform.ANDROID);
  });

  /**
   * @function testRespondsToInitializeSetBreakpointsAndDisconnectRequests
   * @description Tests whether initialize, setBreakpoints, and disconnect requests are handled correctly.
   * @returns {Promise<void>} - Returns a Promise that resolves when the async test assertions have completed.
   */
  test('responds to initialize, setBreakpoints, and disconnect requests', async () => {
    await activateCjmpExtension();
    const adapter = createKeelsDebugAdapterForTest(
      new StaticDeviceManager() as unknown as KeelsDeviceManager,
      { configuration: {} } as vscode.DebugSession,
      createTestExtensionContext(),
    );
    const messages: DebugProtocol.ProtocolMessage[] = [];
    const subscription = adapter.onDidSendMessage((message) => {
      messages.push(message as DebugProtocol.ProtocolMessage);
    });

    await adapter.handleMessage({ type: 'request', seq: 1, command: 'initialize' } as DebugProtocol.Request);
    await adapter.handleMessage({
      type: 'request',
      seq: 2,
      command: 'setBreakpoints',
      arguments: { lines: [3, 7] },
    } as DebugProtocol.Request);
    await adapter.handleMessage({ type: 'request', seq: 3, command: 'disconnect' } as DebugProtocol.Request);

    subscription.dispose();
    adapter.dispose();

    const initialize = messages.find((message) => message.type === 'response' && (message as DebugProtocol.Response).command === 'initialize') as DebugProtocol.InitializeResponse;
    assert.strictEqual(initialize.success, true);
    assert.ok(initialize.body);
    assert.strictEqual(initialize.body.supportsConfigurationDoneRequest, true);

    const breakpoints = messages.find((message) => message.type === 'response' && (message as DebugProtocol.Response).command === 'setBreakpoints') as DebugProtocol.SetBreakpointsResponse;
    assert.strictEqual(breakpoints.success, true);
    assert.deepStrictEqual(breakpoints.body.breakpoints, [
      { verified: true, line: 3 },
      { verified: true, line: 7 },
    ]);

    const disconnect = messages.find((message) => message.type === 'response' && (message as DebugProtocol.Response).command === 'disconnect') as DebugProtocol.DisconnectResponse;
    assert.strictEqual(disconnect.success, true);
  });
});

class StaticDeviceManager {
  async showDevicePicker(): Promise<Device> {
    return {
      name: 'Android Test Device',
      id: 'test-android-device',
      isSupported: true,
      targetPlatform: 'android-arm64',
      emulator: false,
      category: 'mobile',
      platform: 'ANDROID',
    };
  }
}

function createTestExtensionContext(): vscode.ExtensionContext {
  return {
    subscriptions: [],
    workspaceState: createMemento(),
    globalState: createMemento(),
    extensionUri: vscode.Uri.file(process.cwd()),
    extensionPath: process.cwd(),
    environmentVariableCollection: {} as vscode.GlobalEnvironmentVariableCollection,
    extensionMode: vscode.ExtensionMode.Test,
    extension: {} as vscode.Extension<unknown>,
    secrets: {} as vscode.SecretStorage,
    storageUri: undefined,
    storagePath: undefined,
    globalStorageUri: vscode.Uri.file(process.cwd()),
    globalStoragePath: process.cwd(),
    logUri: vscode.Uri.file(process.cwd()),
    logPath: process.cwd(),
    asAbsolutePath: (relativePath: string) => path.join(process.cwd(), relativePath),
  } as unknown as vscode.ExtensionContext;
}

function createMemento(): vscode.Memento {
  const values = new Map<string, unknown>();
  return {
    keys: () => [...values.keys()],
    get: <T>(key: string, defaultValue?: T) => (values.has(key) ? values.get(key) : defaultValue) as T,
    update: async (key: string, value: unknown) => {
      values.set(key, value);
    },
  };
}