import {
  CONTENT_MESSAGE_TYPE,
  MAIN_MESSAGE_TYPE,
} from "common/Actions.sys.mjs";
import Joi from "joi-browser";

export const baseKeys = {
  // client_id will be set by PingCentre if it doesn't exist.
  client_id: Joi.string().optional(),
  addon_version: Joi.string().required(),
  locale: Joi.string().required(),
  session_id: Joi.string(),
  page: Joi.valid([
    "about:home",
    "about:newtab",
    "about:welcome",
    "both",
    "unknown",
  ]),
  user_prefs: Joi.number().integer().required(),
};

export const BasePing = Joi.object()
  .keys(baseKeys)
  .options({ allowUnknown: true });

export const eventsTelemetryExtraKeys = Joi.object()
  .keys({
    session_id: baseKeys.session_id.required(),
    page: baseKeys.page.required(),
    addon_version: baseKeys.addon_version.required(),
    user_prefs: baseKeys.user_prefs.required(),
    action_position: Joi.string().optional(),
  })
  .options({ allowUnknown: false });

export const UserEventPing = Joi.object().keys(
  Object.assign({}, baseKeys, {
    session_id: baseKeys.session_id.required(),
    page: baseKeys.page.required(),
    source: Joi.string(),
    event: Joi.string().required(),
    action: Joi.valid("activity_stream_user_event").required(),
    metadata_source: Joi.string(),
    highlight_type: Joi.valid(["bookmarks", "recommendation", "history"]),
    recommender_type: Joi.string(),
    value: Joi.object().keys({
      newtab_url_category: Joi.string(),
      newtab_extension_id: Joi.string(),
      home_url_category: Joi.string(),
      home_extension_id: Joi.string(),
    }),
  })
);

export const UTUserEventPing = Joi.array().items(
  Joi.string().required().valid("activity_stream"),
  Joi.string().required().valid("event"),
  Joi.string()
    .required()
    .valid([
      "CLICK",
      "SEARCH",
      "BLOCK",
      "DELETE",
      "DELETE_CONFIRM",
      "DIALOG_CANCEL",
      "DIALOG_OPEN",
      "OPEN_NEW_WINDOW",
      "OPEN_PRIVATE_WINDOW",
      "OPEN_NEWTAB_PREFS",
      "CLOSE_NEWTAB_PREFS",
      "BOOKMARK_DELETE",
      "BOOKMARK_ADD",
      "PIN",
      "UNPIN",
      "SAVE_TO_POCKET",
    ]),
  Joi.string().required(),
  eventsTelemetryExtraKeys
);

// Use this to validate actions generated from Redux
export const UserEventAction = Joi.object().keys({
  type: Joi.string().required(),
  data: Joi.object()
    .keys({
      event: Joi.valid([
        "CLICK",
        "SEARCH",
        "SEARCH_HANDOFF",
        "BLOCK",
        "DELETE",
        "DELETE_CONFIRM",
        "DIALOG_CANCEL",
        "DIALOG_OPEN",
        "OPEN_NEW_WINDOW",
        "OPEN_PRIVATE_WINDOW",
        "OPEN_NEWTAB_PREFS",
        "CLOSE_NEWTAB_PREFS",
        "BOOKMARK_DELETE",
        "BOOKMARK_ADD",
        "PIN",
        "PREVIEW_REQUEST",
        "UNPIN",
        "SAVE_TO_POCKET",
        "MENU_MOVE_UP",
        "MENU_MOVE_DOWN",
        "SCREENSHOT_REQUEST",
        "MENU_REMOVE",
        "MENU_COLLAPSE",
        "MENU_EXPAND",
        "MENU_MANAGE",
        "MENU_ADD_TOPSITE",
        "MENU_PRIVACY_NOTICE",
        "DELETE_FROM_POCKET",
        "ARCHIVE_FROM_POCKET",
        "SKIPPED_SIGNIN",
        "SUBMIT_EMAIL",
        "SUBMIT_SIGNIN",
        "SHOW_PRIVACY_INFO",
        "CLICK_PRIVACY_INFO",
      ]).required(),
      source: Joi.valid(["TOP_SITES", "TOP_STORIES", "HIGHLIGHTS"]),
      action_position: Joi.number().integer(),
      value: Joi.object().keys({
        icon_type: Joi.valid([
          "tippytop",
          "rich_icon",
          "screenshot_with_icon",
          "screenshot",
          "no_image",
          "custom_screenshot",
        ]),
        card_type: Joi.valid([
          "bookmark",
          "trending",
          "pinned",
          "pocket",
          "search",
          "spoc",
          "organic",
        ]),
        search_vendor: Joi.valid(["google", "amazon"]),
        has_flow_params: Joi.bool(),
      }),
    })
    .required(),
  meta: Joi.object()
    .keys({
      to: Joi.valid(MAIN_MESSAGE_TYPE).required(),
      from: Joi.valid(CONTENT_MESSAGE_TYPE).required(),
    })
    .required(),
});

export const TileSchema = Joi.object().keys({
  id: Joi.number().integer().required(),
  pos: Joi.number().integer(),
});

export const ImpressionStatsPing = Joi.object().keys(
  Object.assign({}, baseKeys, {
    source: Joi.string().required(),
    impression_id: Joi.string().required(),
    tiles: Joi.array().items(TileSchema).required(),
    click: Joi.number().integer(),
    block: Joi.number().integer(),
    pocket: Joi.number().integer(),
  })
);

export const SessionPing = Joi.object().keys(
  Object.assign({}, baseKeys, {
    session_id: baseKeys.session_id.required(),
    page: baseKeys.page.required(),
    session_duration: Joi.number().integer(),
    action: Joi.valid("activity_stream_session").required(),
    profile_creation_date: Joi.number().integer(),
    perf: Joi.object()
      .keys({
        // How long it took in ms for data to be ready for display.
        highlights_data_late_by_ms: Joi.number().positive(),

        // Timestamp of the action perceived by the user to trigger the load
        // of this page.
        //
        // Not required at least for the error cases where the
        // observer event doesn't fire
        load_trigger_ts: Joi.number()
          .integer()
          .notes(["server counter", "server counter alert"]),

        // What was the perceived trigger of the load action?
        //
        // Not required at least for the error cases where the observer event
        // doesn't fire
        load_trigger_type: Joi.valid([
          "first_window_opened",
          "menu_plus_or_keyboard",
          "unexpected",
        ])
          .notes(["server counter", "server counter alert"])
          .required(),

        // How long it took in ms for data to be ready for display.
        topsites_data_late_by_ms: Joi.number().positive(),

        // When did the topsites element finish painting?  Note that, at least for
        // the first tab to be loaded, and maybe some others, this will be before
        // topsites has yet to receive screenshots updates from the add-on code,
        // and is therefore just showing placeholder screenshots.
        topsites_first_painted_ts: Joi.number()
          .integer()
          .notes(["server counter", "server counter alert"]),

        // Information about the quality of TopSites images and icons.
        topsites_icon_stats: Joi.object().keys({
          custom_screenshot: Joi.number(),
          rich_icon: Joi.number(),
          screenshot: Joi.number(),
          screenshot_with_icon: Joi.number(),
          tippytop: Joi.number(),
          no_image: Joi.number(),
        }),

        // The count of pinned Top Sites.
        topsites_pinned: Joi.number(),

        // The count of search shortcut Top Sites.
        topsites_search_shortcuts: Joi.number(),

        // When the page itself receives an event that document.visibilityState
        // == visible.
        //
        // Not required at least for the (error?) case where the
        // visibility_event doesn't fire.  (It's not clear whether this
        // can happen in practice, but if it does, we'd like to know about it).
        visibility_event_rcvd_ts: Joi.number()
          .integer()
          .notes(["server counter", "server counter alert"]),

        // The boolean to signify whether the page is preloaded or not.
        is_preloaded: Joi.bool().required(),
      })
      .required(),
  })
);

export const ASRouterEventPing = Joi.object()
  .keys({
    addon_version: Joi.string().required(),
    locale: Joi.string().required(),
    message_id: Joi.string().required(),
    event: Joi.string().required(),
    client_id: Joi.string(),
    impression_id: Joi.string(),
  })
  .or("client_id", "impression_id");

export const UTSessionPing = Joi.array().items(
  Joi.string().required().valid("activity_stream"),
  Joi.string().required().valid("end"),
  Joi.string().required().valid("session"),
  Joi.string().required(),
  eventsTelemetryExtraKeys
);

export function chaiAssertions(_chai, utils) {
  const { Assertion } = _chai;

  Assertion.addMethod("validate", function (schema, schemaName) {
    const { error } = Joi.validate(this._obj, schema, { allowUnknown: false });
    this.assert(
      !error,
      `Expected to be ${
        schemaName ? `a valid ${schemaName}` : "valid"
      } but there were errors: ${error}`
    );
  });

  const assertions = {
    /**
     * assert.validate - Validates an item given a Joi schema
     *
     * @param  {any} actual The item to validate
     * @param  {obj} schema A Joi schema
     */
    validate(actual, schema, schemaName) {
      new Assertion(actual).validate(schema, schemaName);
    },

    /**
     * isUserEventAction - Passes if the item is a valid UserEvent action
     *
     * @param  {any} actual The item to validate
     */
    isUserEventAction(actual) {
      new Assertion(actual).validate(UserEventAction, "UserEventAction");
    },
  };

  Object.assign(_chai.assert, assertions);
}
