// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import * as i18n from '../../core/i18n/i18n.js';
import * as SDK from '../../core/sdk/sdk.js';
import type * as ProtocolProxyApi from '../../generated/protocol-proxy-api.js';
import * as Protocol from '../../generated/protocol.js';

const UIStrings = {
  /**
   * @description Text in Security Panel of the Security panel
   */
  theSecurityOfThisPageIsUnknown: 'The security of this page is unknown.',
  /**
   * @description Text in Security Panel of the Security panel
   */
  thisPageIsNotSecure: 'This page is not secure.',
  /**
   * @description Text in Security Panel of the Security panel
   */
  thisPageIsSecureValidHttps: 'This page is secure (valid HTTPS).',
  /**
   * @description Text in Security Panel of the Security panel
   */
  thisPageIsNotSecureBrokenHttps: 'This page is not secure (broken HTTPS).',
  /**
   * @description Description of an SSL cipher that contains a separate (bulk) cipher and MAC.
   * @example {AES_256_CBC} PH1
   * @example {HMAC-SHA1} PH2
   */
  cipherWithMAC: '{PH1} with {PH2}',
  /**
   * @description Description of an SSL Key and its Key Exchange Group.
   * @example {ECDHE_RSA} PH1
   * @example {X25519} PH2
   */
  keyExchangeWithGroup: '{PH1} with {PH2}',
} as const;
const str_ = i18n.i18n.registerUIStrings('panels/security/SecurityModel.ts', UIStrings);
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
const i18nLazyString = i18n.i18n.getLazilyComputedLocalizedString.bind(undefined, str_);

export class SecurityModel extends SDK.SDKModel.SDKModel<EventTypes> {
  private readonly dispatcher: SecurityDispatcher;
  private readonly securityAgent: ProtocolProxyApi.SecurityApi;
  constructor(target: SDK.Target.Target) {
    super(target);
    this.dispatcher = new SecurityDispatcher(this);
    this.securityAgent = target.securityAgent();
    target.registerSecurityDispatcher(this.dispatcher);
    void this.securityAgent.invoke_enable();
  }

  resourceTreeModel(): SDK.ResourceTreeModel.ResourceTreeModel {
    return this.target().model(SDK.ResourceTreeModel.ResourceTreeModel) as SDK.ResourceTreeModel.ResourceTreeModel;
  }

  networkManager(): SDK.NetworkManager.NetworkManager {
    return this.target().model(SDK.NetworkManager.NetworkManager) as SDK.NetworkManager.NetworkManager;
  }
}

export function securityStateCompare(a: Protocol.Security.SecurityState, b: Protocol.Security.SecurityState): number {
  const SECURITY_STATE_ORDER = [
    Protocol.Security.SecurityState.Info,
    Protocol.Security.SecurityState.InsecureBroken,
    Protocol.Security.SecurityState.Insecure,
    Protocol.Security.SecurityState.Neutral,
    Protocol.Security.SecurityState.Secure,
    // Unknown is max so that failed/cancelled requests don't overwrite the origin security state for successful requests,
    // and so that failed/cancelled requests appear at the bottom of the origins list.
    Protocol.Security.SecurityState.Unknown,
  ];

  return SECURITY_STATE_ORDER.indexOf(a) - SECURITY_STATE_ORDER.indexOf(b);
}

SDK.SDKModel.SDKModel.register(SecurityModel, {capabilities: SDK.Target.Capability.SECURITY, autostart: false});

export enum Events {
  /* eslint-disable @typescript-eslint/naming-convention -- Used by web_tests. */
  VisibleSecurityStateChanged = 'VisibleSecurityStateChanged',
  /* eslint-enable @typescript-eslint/naming-convention */
}

export interface EventTypes {
  [Events.VisibleSecurityStateChanged]: PageVisibleSecurityState;
}

export const SummaryMessages: Record<string, () => string> = {
  [Protocol.Security.SecurityState.Unknown]: i18nLazyString(UIStrings.theSecurityOfThisPageIsUnknown),
  [Protocol.Security.SecurityState.Insecure]: i18nLazyString(UIStrings.thisPageIsNotSecure),
  [Protocol.Security.SecurityState.Neutral]: i18nLazyString(UIStrings.thisPageIsNotSecure),
  [Protocol.Security.SecurityState.Secure]: i18nLazyString(UIStrings.thisPageIsSecureValidHttps),
  [Protocol.Security.SecurityState.InsecureBroken]: i18nLazyString(UIStrings.thisPageIsNotSecureBrokenHttps),
};

export class PageVisibleSecurityState {
  securityState: Protocol.Security.SecurityState;
  certificateSecurityState: CertificateSecurityState|null;
  safetyTipInfo: SafetyTipInfo|null;
  securityStateIssueIds: string[];
  constructor(
      securityState: Protocol.Security.SecurityState,
      certificateSecurityState: Protocol.Security.CertificateSecurityState|null,
      safetyTipInfo: Protocol.Security.SafetyTipInfo|null, securityStateIssueIds: string[]) {
    this.securityState = securityState;
    this.certificateSecurityState =
        certificateSecurityState ? new CertificateSecurityState(certificateSecurityState) : null;
    this.safetyTipInfo = safetyTipInfo ? new SafetyTipInfo(safetyTipInfo) : null;
    this.securityStateIssueIds = securityStateIssueIds;
  }
}

export class CertificateSecurityState {
  protocol: string;
  keyExchange: string;
  keyExchangeGroup: string|null;
  cipher: string;
  mac: string|null;
  certificate: string[];
  subjectName: string;
  issuer: string;
  validFrom: number;
  validTo: number;
  certificateNetworkError: string|null;
  certificateHasWeakSignature: boolean;
  certificateHasSha1Signature: boolean;
  modernSSL: boolean;
  obsoleteSslProtocol: boolean;
  obsoleteSslKeyExchange: boolean;
  obsoleteSslCipher: boolean;
  obsoleteSslSignature: boolean;
  constructor(certificateSecurityState: Protocol.Security.CertificateSecurityState) {
    this.protocol = certificateSecurityState.protocol;
    this.keyExchange = certificateSecurityState.keyExchange;
    this.keyExchangeGroup = certificateSecurityState.keyExchangeGroup || null;
    this.cipher = certificateSecurityState.cipher;
    this.mac = certificateSecurityState.mac || null;
    this.certificate = certificateSecurityState.certificate;
    this.subjectName = certificateSecurityState.subjectName;
    this.issuer = certificateSecurityState.issuer;
    this.validFrom = certificateSecurityState.validFrom;
    this.validTo = certificateSecurityState.validTo;
    this.certificateNetworkError = certificateSecurityState.certificateNetworkError || null;
    this.certificateHasWeakSignature = certificateSecurityState.certificateHasWeakSignature;
    this.certificateHasSha1Signature = certificateSecurityState.certificateHasSha1Signature;
    this.modernSSL = certificateSecurityState.modernSSL;
    this.obsoleteSslProtocol = certificateSecurityState.obsoleteSslProtocol;
    this.obsoleteSslKeyExchange = certificateSecurityState.obsoleteSslKeyExchange;
    this.obsoleteSslCipher = certificateSecurityState.obsoleteSslCipher;
    this.obsoleteSslSignature = certificateSecurityState.obsoleteSslSignature;
  }

  isCertificateExpiringSoon(): boolean {
    const expiryDate = new Date(this.validTo * 1000).getTime();
    return (expiryDate < new Date(Date.now()).setHours(48)) && (expiryDate > Date.now());
  }

  getKeyExchangeName(): string {
    if (this.keyExchangeGroup) {
      return this.keyExchange ?
          i18nString(UIStrings.keyExchangeWithGroup, {PH1: this.keyExchange, PH2: this.keyExchangeGroup}) :
          this.keyExchangeGroup;
    }
    return this.keyExchange;
  }

  getCipherFullName(): string {
    return this.mac ? i18nString(UIStrings.cipherWithMAC, {PH1: this.cipher, PH2: this.mac}) : this.cipher;
  }
}

class SafetyTipInfo {
  safetyTipStatus: string;
  safeUrl: string|null;
  constructor(safetyTipInfo: Protocol.Security.SafetyTipInfo) {
    this.safetyTipStatus = safetyTipInfo.safetyTipStatus;
    this.safeUrl = safetyTipInfo.safeUrl || null;
  }
}

export class SecurityStyleExplanation {
  securityState: Protocol.Security.SecurityState;
  title: string|undefined;
  summary: string;
  description: string;
  certificate: string[];
  mixedContentType: Protocol.Security.MixedContentType;
  recommendations: string[];
  constructor(
      securityState: Protocol.Security.SecurityState, title: string|undefined, summary: string, description: string,
      certificate: string[]|undefined = [],
      mixedContentType: Protocol.Security.MixedContentType|undefined = Protocol.Security.MixedContentType.None,
      recommendations: string[]|undefined = []) {
    this.securityState = securityState;
    this.title = title;
    this.summary = summary;
    this.description = description;
    this.certificate = certificate;
    this.mixedContentType = mixedContentType;
    this.recommendations = recommendations;
  }
}

class SecurityDispatcher implements ProtocolProxyApi.SecurityDispatcher {
  private readonly model: SecurityModel;
  constructor(model: SecurityModel) {
    this.model = model;
  }

  securityStateChanged(_event: Protocol.Security.SecurityStateChangedEvent): void {
  }

  visibleSecurityStateChanged({visibleSecurityState}: Protocol.Security.VisibleSecurityStateChangedEvent): void {
    const pageVisibleSecurityState = new PageVisibleSecurityState(
        visibleSecurityState.securityState, visibleSecurityState.certificateSecurityState || null,
        visibleSecurityState.safetyTipInfo || null, visibleSecurityState.securityStateIssueIds);
    this.model.dispatchEventToListeners(Events.VisibleSecurityStateChanged, pageVisibleSecurityState);
  }

  certificateError(_event: Protocol.Security.CertificateErrorEvent): void {
  }
}
