import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
import { StateFactory } from "@bitwarden/common/platform/factories/state-factory";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { GlobalState } from "@bitwarden/common/platform/models/domain/global-state";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type";
import { CipherType } from "@bitwarden/common/vault/enums/cipher-type";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";

import {
  authServiceFactory,
  AuthServiceInitOptions,
} from "../../auth/background/service-factories/auth-service.factory";
import { Account } from "../../models/account";
import { CachedServices } from "../../platform/background/service-factories/factory-options";
import { BrowserApi } from "../../platform/browser/browser-api";
import {
  cipherServiceFactory,
  CipherServiceInitOptions,
} from "../../vault/background/service_factories/cipher-service.factory";

import { MainContextMenuHandler } from "./main-context-menu-handler";

const NOT_IMPLEMENTED = (..._args: unknown[]) => Promise.resolve();

const LISTENED_TO_COMMANDS = [
  "loggedIn",
  "unlocked",
  "syncCompleted",
  "bgUpdateContextMenu",
  "editedCipher",
  "addedCipher",
  "deletedCipher",
];

export class CipherContextMenuHandler {
  constructor(
    private mainContextMenuHandler: MainContextMenuHandler,
    private authService: AuthService,
    private cipherService: CipherService
  ) {}

  static async create(cachedServices: CachedServices) {
    const stateFactory = new StateFactory(GlobalState, Account);
    const serviceOptions: AuthServiceInitOptions & CipherServiceInitOptions = {
      apiServiceOptions: {
        logoutCallback: NOT_IMPLEMENTED,
      },
      cryptoFunctionServiceOptions: {
        win: self,
      },
      encryptServiceOptions: {
        logMacFailures: false,
      },
      i18nServiceOptions: {
        systemLanguage: chrome.i18n.getUILanguage(),
      },
      keyConnectorServiceOptions: {
        logoutCallback: NOT_IMPLEMENTED,
      },
      logServiceOptions: {
        isDev: false,
      },
      platformUtilsServiceOptions: {
        biometricCallback: () => Promise.resolve(false),
        clipboardWriteCallback: NOT_IMPLEMENTED,
        win: self,
      },
      stateMigrationServiceOptions: {
        stateFactory: stateFactory,
      },
      stateServiceOptions: {
        stateFactory: stateFactory,
      },
    };
    return new CipherContextMenuHandler(
      await MainContextMenuHandler.mv3Create(cachedServices),
      await authServiceFactory(cachedServices, serviceOptions),
      await cipherServiceFactory(cachedServices, serviceOptions)
    );
  }

  static async tabsOnActivatedListener(
    activeInfo: chrome.tabs.TabActiveInfo,
    serviceCache: CachedServices
  ) {
    const cipherContextMenuHandler = await CipherContextMenuHandler.create(serviceCache);
    const tab = await BrowserApi.getTab(activeInfo.tabId);
    await cipherContextMenuHandler.update(tab.url);
  }

  static async tabsOnReplacedListener(
    addedTabId: number,
    removedTabId: number,
    serviceCache: CachedServices
  ) {
    const cipherContextMenuHandler = await CipherContextMenuHandler.create(serviceCache);
    const tab = await BrowserApi.getTab(addedTabId);
    await cipherContextMenuHandler.update(tab.url);
  }

  static async tabsOnUpdatedListener(
    tabId: number,
    changeInfo: chrome.tabs.TabChangeInfo,
    tab: chrome.tabs.Tab,
    serviceCache: CachedServices
  ) {
    if (changeInfo.status !== "complete") {
      return;
    }
    const cipherContextMenuHandler = await CipherContextMenuHandler.create(serviceCache);
    await cipherContextMenuHandler.update(tab.url);
  }

  static async messageListener(
    message: { command: string },
    sender: chrome.runtime.MessageSender,
    cachedServices: CachedServices
  ) {
    if (!CipherContextMenuHandler.shouldListen(message)) {
      return;
    }
    const cipherContextMenuHandler = await CipherContextMenuHandler.create(cachedServices);
    await cipherContextMenuHandler.messageListener(message);
  }

  private static shouldListen(message: { command: string }) {
    return LISTENED_TO_COMMANDS.includes(message.command);
  }

  async messageListener(message: { command: string }, sender?: chrome.runtime.MessageSender) {
    if (!CipherContextMenuHandler.shouldListen(message)) {
      return;
    }

    const activeTabs = await BrowserApi.getActiveTabs();
    if (!activeTabs || activeTabs.length === 0) {
      return;
    }

    await this.update(activeTabs[0].url);
  }

  async update(url: string) {
    const authStatus = await this.authService.getAuthStatus();
    await MainContextMenuHandler.removeAll();
    if (authStatus !== AuthenticationStatus.Unlocked) {
      // Should I pass in the auth status or even have two separate methods for this
      // on MainContextMenuHandler
      await this.mainContextMenuHandler.noAccess();
      return;
    }

    const menuEnabled = await this.mainContextMenuHandler.init();
    if (!menuEnabled) {
      return;
    }

    const ciphers = await this.cipherService.getAllDecryptedForUrl(url);
    ciphers.sort((a, b) => this.cipherService.sortCiphersByLastUsedThenName(a, b));

    if (ciphers.length === 0) {
      await this.mainContextMenuHandler.noLogins(url);
      return;
    }

    for (const cipher of ciphers) {
      await this.updateForCipher(url, cipher);
    }
  }

  private async updateForCipher(url: string, cipher: CipherView) {
    if (
      cipher == null ||
      cipher.type !== CipherType.Login ||
      cipher.reprompt !== CipherRepromptType.None
    ) {
      return;
    }

    let title = cipher.name;
    if (!Utils.isNullOrEmpty(title)) {
      title += ` (${cipher.login.username})`;
    }

    await this.mainContextMenuHandler.loadOptions(title, cipher.id, url, cipher);
  }
}
