/* eslint-disable @typescript-eslint/no-explicit-any */
import {
  BrowserSessionData,
  BrowserSyncData,
  BrowserSyncFlow,
  CryptoHelper,
  SignerMetaData,
  Identity_DECRYPTED,
  Nip07Method,
  Nip07MethodPolicy,
  NostrHelper,
  Permission_DECRYPTED,
  Permission_ENCRYPTED,
} from '@common';
import { Event, EventTemplate, finalizeEvent, nip04, nip44 } from 'nostr-tools';
import { FirefoxMetaHandler } from './app/common/data/firefox-meta-handler';
import browser from 'webextension-polyfill';
import { Buffer } from 'buffer';

export const debug = function (message: any) {
  const dateString = new Date().toISOString();
  console.log(`[Plebeian Signer - ${dateString}]: ${JSON.stringify(message)}`);
};

export type PromptResponse =
  | 'reject'
  | 'reject-once'
  | 'approve'
  | 'approve-once';

export interface PromptResponseMessage {
  id: string;
  response: PromptResponse;
}

export interface BackgroundRequestMessage {
  method: Nip07Method;
  params: any;
  host: string;
}

export const getBrowserSessionData = async function (): Promise<
  BrowserSessionData | undefined
> {
  const browserSessionData = await browser.storage.session.get(null);
  if (Object.keys(browserSessionData).length === 0) {
    return undefined;
  }

  return browserSessionData as unknown as BrowserSessionData;
};

export const getSignerMetaData = async function (): Promise<SignerMetaData> {
  const signerMetaHandler = new FirefoxMetaHandler();
  return (await signerMetaHandler.loadFullData()) as SignerMetaData;
};

/**
 * Check if reckless mode should auto-approve the request.
 * Returns true if should auto-approve, false if should use normal permission flow.
 *
 * Logic:
 * - If reckless mode is OFF → return false (use normal flow)
 * - If reckless mode is ON and whitelist is empty → return true (approve all)
 * - If reckless mode is ON and whitelist has entries → return true only if host is in whitelist
 */
export const shouldRecklessModeApprove = async function (
  host: string
): Promise<boolean> {
  const signerMetaData = await getSignerMetaData();
  debug(`shouldRecklessModeApprove: recklessMode=${signerMetaData.recklessMode}, host=${host}`);
  debug(`Full signerMetaData: ${JSON.stringify(signerMetaData)}`);

  if (!signerMetaData.recklessMode) {
    return false;
  }

  const whitelistedHosts = signerMetaData.whitelistedHosts ?? [];

  if (whitelistedHosts.length === 0) {
    // Reckless mode ON, no whitelist → approve all
    return true;
  }

  // Reckless mode ON, whitelist has entries → only approve if host is whitelisted
  return whitelistedHosts.includes(host);
};

export const getBrowserSyncData = async function (): Promise<
  BrowserSyncData | undefined
> {
  const signerMetaHandler = new FirefoxMetaHandler();
  const signerMetaData =
    (await signerMetaHandler.loadFullData()) as SignerMetaData;

  let browserSyncData: BrowserSyncData | undefined;

  if (signerMetaData.syncFlow === BrowserSyncFlow.NO_SYNC) {
    browserSyncData = (await browser.storage.local.get(
      null
    )) as unknown as BrowserSyncData;
  } else if (signerMetaData.syncFlow === BrowserSyncFlow.BROWSER_SYNC) {
    browserSyncData = (await browser.storage.sync.get(
      null
    )) as unknown as BrowserSyncData;
  }

  return browserSyncData;
};

export const savePermissionsToBrowserSyncStorage = async function (
  permissions: Permission_ENCRYPTED[]
): Promise<void> {
  const signerMetaHandler = new FirefoxMetaHandler();
  const signerMetaData =
    (await signerMetaHandler.loadFullData()) as SignerMetaData;

  if (signerMetaData.syncFlow === BrowserSyncFlow.NO_SYNC) {
    await browser.storage.local.set({ permissions });
  } else if (signerMetaData.syncFlow === BrowserSyncFlow.BROWSER_SYNC) {
    await browser.storage.sync.set({ permissions });
  }
};

export const checkPermissions = function (
  browserSessionData: BrowserSessionData,
  identity: Identity_DECRYPTED,
  host: string,
  method: Nip07Method,
  params: any
): boolean | undefined {
  const permissions = browserSessionData.permissions.filter(
    (x) =>
      x.identityId === identity.id && x.host === host && x.method === method
  );

  if (permissions.length === 0) {
    return undefined;
  }

  if (method === 'getPublicKey') {
    // No evaluation of params required.
    return permissions.every((x) => x.methodPolicy === 'allow');
  }

  if (method === 'getRelays') {
    // No evaluation of params required.
    return permissions.every((x) => x.methodPolicy === 'allow');
  }

  if (method === 'signEvent') {
    // Evaluate params.
    const eventTemplate = params as EventTemplate;
    if (
      permissions.find(
        (x) => x.methodPolicy === 'allow' && typeof x.kind === 'undefined'
      )
    ) {
      return true;
    }

    if (
      permissions.some(
        (x) => x.methodPolicy === 'allow' && x.kind === eventTemplate.kind
      )
    ) {
      return true;
    }

    if (
      permissions.some(
        (x) => x.methodPolicy === 'deny' && x.kind === eventTemplate.kind
      )
    ) {
      return false;
    }

    return undefined;
  }

  if (method === 'nip04.encrypt') {
    // No evaluation of params required.
    return permissions.every((x) => x.methodPolicy === 'allow');
  }

  if (method === 'nip44.encrypt') {
    // No evaluation of params required.
    return permissions.every((x) => x.methodPolicy === 'allow');
  }

  if (method === 'nip04.decrypt') {
    // No evaluation of params required.
    return permissions.every((x) => x.methodPolicy === 'allow');
  }

  if (method === 'nip44.decrypt') {
    // No evaluation of params required.
    return permissions.every((x) => x.methodPolicy === 'allow');
  }

  return undefined;
};

export const storePermission = async function (
  browserSessionData: BrowserSessionData,
  identity: Identity_DECRYPTED,
  host: string,
  method: Nip07Method,
  methodPolicy: Nip07MethodPolicy,
  kind?: number
) {
  const browserSyncData = await getBrowserSyncData();
  if (!browserSyncData) {
    throw new Error(`Could not retrieve sync data`);
  }

  const permission: Permission_DECRYPTED = {
    id: crypto.randomUUID(),
    identityId: identity.id,
    host,
    method,
    methodPolicy,
    kind,
  };

  // Store session data
  await browser.storage.session.set({
    permissions: [...browserSessionData.permissions, permission],
  });

  // Encrypt permission to store in sync storage (depending on sync flow).
  const encryptedPermission = await encryptPermission(
    permission,
    browserSessionData
  );

  await savePermissionsToBrowserSyncStorage([
    ...browserSyncData.permissions,
    encryptedPermission,
  ]);
};

export const getPosition = async function (width: number, height: number) {
  let left = 0;
  let top = 0;

  try {
    const lastFocused = await browser.windows.getLastFocused();

    if (
      lastFocused &&
      lastFocused.top !== undefined &&
      lastFocused.left !== undefined &&
      lastFocused.width !== undefined &&
      lastFocused.height !== undefined
    ) {
      // Position window in the center of the lastFocused window
      top = Math.round(lastFocused.top + (lastFocused.height - height) / 2);
      left = Math.round(lastFocused.left + (lastFocused.width - width) / 2);
    } else {
      console.error('Last focused window properties are undefined.');
    }
  } catch (error) {
    console.error('Error getting window position:', error);
  }

  return {
    top,
    left,
  };
};

export const signEvent = function (
  eventTemplate: EventTemplate,
  privkey: string
): Event {
  return finalizeEvent(eventTemplate, NostrHelper.hex2bytes(privkey));
};

export const nip04Encrypt = async function (
  privkey: string,
  peerPubkey: string,
  plaintext: string
): Promise<string> {
  return await nip04.encrypt(
    NostrHelper.hex2bytes(privkey),
    peerPubkey,
    plaintext
  );
};

export const nip44Encrypt = async function (
  privkey: string,
  peerPubkey: string,
  plaintext: string
): Promise<string> {
  const key = nip44.v2.utils.getConversationKey(
    NostrHelper.hex2bytes(privkey),
    peerPubkey
  );
  return nip44.v2.encrypt(plaintext, key);
};

export const nip04Decrypt = async function (
  privkey: string,
  peerPubkey: string,
  ciphertext: string
): Promise<string> {
  return await nip04.decrypt(
    NostrHelper.hex2bytes(privkey),
    peerPubkey,
    ciphertext
  );
};

export const nip44Decrypt = async function (
  privkey: string,
  peerPubkey: string,
  ciphertext: string
): Promise<string> {
  const key = nip44.v2.utils.getConversationKey(
    NostrHelper.hex2bytes(privkey),
    peerPubkey
  );

  return nip44.v2.decrypt(ciphertext, key);
};

const encryptPermission = async function (
  permission: Permission_DECRYPTED,
  sessionData: BrowserSessionData
): Promise<Permission_ENCRYPTED> {
  const encryptedPermission: Permission_ENCRYPTED = {
    id: await encrypt(permission.id, sessionData),
    identityId: await encrypt(permission.identityId, sessionData),
    host: await encrypt(permission.host, sessionData),
    method: await encrypt(permission.method, sessionData),
    methodPolicy: await encrypt(permission.methodPolicy, sessionData),
  };

  if (typeof permission.kind !== 'undefined') {
    encryptedPermission.kind = await encrypt(
      permission.kind.toString(),
      sessionData
    );
  }

  return encryptedPermission;
};

const encrypt = async function (
  value: string,
  sessionData: BrowserSessionData
): Promise<string> {
  // v2: Use pre-derived key with AES-GCM directly
  if (sessionData.vaultKey) {
    const keyBytes = Buffer.from(sessionData.vaultKey, 'base64');
    const iv = Buffer.from(sessionData.iv, 'base64');

    const key = await crypto.subtle.importKey(
      'raw',
      keyBytes,
      { name: 'AES-GCM' },
      false,
      ['encrypt']
    );

    const cipherText = await crypto.subtle.encrypt(
      { name: 'AES-GCM', iv },
      key,
      new TextEncoder().encode(value)
    );

    return Buffer.from(cipherText).toString('base64');
  }

  // v1: Use password with PBKDF2
  return await CryptoHelper.encrypt(value, sessionData.iv, sessionData.vaultPassword!);
};
