// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { parse } from "tldts";

import { I18nService } from "../../platform/abstractions/i18n.service";
import { Utils } from "../../platform/misc/utils";

import { IntegrationMetadata } from "./integration-metadata";
import { ApiSettings, IntegrationRequest } from "./rpc";

/** Utilities for processing integration settings */
export class IntegrationContext<Settings extends object> {
  /** Instantiates an integration context
   *  @param metadata - defines integration capabilities
   *  @param i18n - localizes error messages
   */
  constructor(
    readonly metadata: IntegrationMetadata,
    protected settings: Settings,
    protected i18n: I18nService,
  ) {}

  /** Lookup the integration's baseUrl
   *  @returns the baseUrl for the API's integration point.
   *   - By default this is defined by the metadata
   *   - When a service allows self-hosting, this can be supplied by `settings`.
   *  @throws a localized error message when a base URL is neither defined by the metadata or
   *   supplied by an argument.
   */
  baseUrl(): string {
    // normalize baseUrl
    const setting =
      (this.settings && "baseUrl" in this.settings
        ? (this.settings.baseUrl as string)
        : undefined) ?? "";
    let result = "";

    // look up definition
    if (this.metadata.selfHost === "always") {
      result = setting;
    } else if (this.metadata.selfHost === "never" || setting.length <= 0) {
      result = this.metadata.baseUrl ?? "";
    } else {
      result = setting;
    }

    // postconditions
    if (result === "") {
      const error = this.i18n.t("forwarderNoUrl", this.metadata.name);
      throw error;
    }

    return result;
  }

  /** look up a service API's authentication token
   *  @param options.base64 when `true`, base64 encodes the result. Defaults to `false`.
   *  @param options.suffix a string to append to the token. Defaults to empty.
   *  @returns the user's authentication token
   *  @throws a localized error message when the token is invalid.
   *  @remarks the string is thrown for backwards compatibility
   */
  authenticationToken(
    options: { base64?: boolean; suffix?: string } = null,
  ): Settings extends ApiSettings ? string : never {
    // normalize `token` then assert it has a value
    let token = "token" in this.settings ? ((this.settings.token as string) ?? "") : "";
    if (token === "") {
      const error = this.i18n.t("forwarderInvalidToken", this.metadata.name);
      throw error;
    }

    // if a suffix exists, it needs to be included before encoding
    token += options?.suffix ?? "";
    if (options?.base64) {
      token = Utils.fromUtf8ToB64(token);
    }

    return token;
  }

  /** look up the website the integration is working with.
   *  @param request supplies information about the state of the extension site
   *  @param options optional parameters
   *  @param options.extractHostname when `true`, tries to extract the hostname from the website URL, returns full URL otherwise
   *  @param options.extractOrigin when `true`, tries to extract the origin (scheme + host) from the website URL, returns full URL otherwise
   *  @param options.maxLength limits the length of the return value
   *  @returns The website or an empty string if a website isn't available
   *  @remarks `website` is usually supplied when generating a credential from the vault
   */
  website(
    request: IntegrationRequest,
    options?: { extractHostname?: boolean; extractOrigin?: boolean; maxLength?: number },
  ) {
    let url = request.website ?? "";
    if (options?.extractOrigin) {
      url = Utils.getUrl(url)?.origin ?? url;
    } else if (options?.extractHostname) {
      url = Utils.getHost(url) ?? url;
    }
    return url.slice(0, options?.maxLength);
  }

  /** look up localized text indicating Bitwarden requested the forwarding address.
   *  @param request supplies information about the state of the extension site
   *  @param options optional parameters
   *  @param options.extractHostname when `true`, extracts the hostname from the website URL
   *  @param options.maxLength limits the length of the return value
   *  @returns localized text describing a generated forwarding address
   */
  generatedBy(
    request: IntegrationRequest,
    options?: { extractHostname?: boolean; maxLength?: number },
  ) {
    const website = this.website(request, { extractHostname: options?.extractHostname ?? false });

    const descriptionId =
      website === "" ? "forwarderGeneratedBy" : "forwarderGeneratedByWithWebsite";
    const description = this.i18n.t(descriptionId, website);

    return description.slice(0, options?.maxLength);
  }

  /** transform a domain into a valid prefix
   * for example, "example.com" becomes "example", "foo.example.com" becomes "foo_example"
   * @param request supplies information about the state of the extension site
   * @returns prefix derived from the website URL or an empty string if a website isn't available
   */
  prefix(request: IntegrationRequest) {
    const rawWebsite = this.website(request);
    const hostname = Utils.getHostname(rawWebsite) ?? "";
    if (hostname === "") {
      return "";
    }

    const parsed = parse(hostname, { allowPrivateDomains: true });

    if (parsed.domainWithoutSuffix != null) {
      // Use tldts-parsed parts so ccSLDs (e.g. .co.uk, .com.au) are handled correctly.
      // subdomain may itself contain dots (foo.bar), so we split on "." before joining with "_".
      return [parsed.subdomain, parsed.domainWithoutSuffix]
        .filter(Boolean)
        .join(".")
        .split(".")
        .join("_");
    }

    // Fallback for IPs and localhost where tldts has no public suffix data.
    const parts = hostname.split(".");
    if (parts.length <= 1) {
      return Utils.getUrl(rawWebsite) != null ? hostname : "";
    }

    // Preserve existing behavior: strip last label and join with "_"
    // (e.g. 127.0.0.1 → "127_0_0")
    return parts.slice(0, parts.length - 1).join("_");
  }
}
