<template>
  <QuickNav selected="Invite" />
  <TopMessage />

  <section id="Content" class="p-6 pb-24 max-w-3xl mx-auto">
    <!-- Back -->
    <div class="text-lg text-center font-light relative px-7">
      <h1
        class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
        @click="$router.back()"
      >
        <font-awesome icon="chevron-left" class="fa-fw"></font-awesome>
      </h1>
    </div>

    <!-- Heading -->
    <h1 class="text-4xl text-center font-light">Invitations</h1>

    <ul class="ml-8 mt-4 list-outside list-disc w-5/6">
      <li>
        Note when sending
        <span
          v-if="!showAppleWarning"
          class="text-blue-500 cursor-pointer"
          @click="showAppleWarning = !showAppleWarning"
        >
          to Apple users...
        </span>
        <span v-else>
          to Apple users: their links often fail because their device cuts off
          part of the link. You might need to send it to them some other way,
          like in an email.
        </span>
      </li>
    </ul>

    <!-- New Project -->
    <button
      v-if="isRegistered"
      class="fixed right-6 top-12 text-center text-4xl leading-none bg-green-600 text-white w-14 py-2.5 rounded-full"
      @click="createInvite()"
    >
      <font-awesome icon="plus" class="fa-fw"></font-awesome>
    </button>

    <InviteDialog ref="inviteDialog" />

    <!-- Invites Table -->
    <div v-if="invites.length" class="mt-6">
      <table class="min-w-full bg-white">
        <thead>
          <tr>
            <th class="py-2">
              ID
              <br />
              (click for link)
            </th>
            <th class="py-2">Notes</th>
            <th class="py-2">Expires At</th>
            <th class="py-2">Redeemed</th>
          </tr>
        </thead>
        <tbody>
          <tr
            v-for="invite in invites"
            :key="invite.inviteIdentifier"
            class="border-t py-2"
          >
            <td>
              <span
                v-if="
                  !invite.redeemedAt &&
                  invite.expiresAt > new Date().toISOString()
                "
                class="text-center text-blue-500 cursor-pointer"
                :title="inviteLink(invite.jwt)"
                @click="
                  copyInviteAndNotify(invite.inviteIdentifier, invite.jwt)
                "
              >
                {{ getTruncatedInviteId(invite.inviteIdentifier) }}
              </span>
              <span
                v-else
                class="text-center text-slate-500 cursor-pointer"
                :title="invite.inviteIdentifier"
                @click="
                  showInvite(
                    invite.inviteIdentifier,
                    !!invite.redeemedAt,
                    invite.expiresAt < new Date().toISOString(),
                  )
                "
              >
                {{ getTruncatedInviteId(invite.inviteIdentifier) }}
              </span>
            </td>
            <td class="text-left" :data-testId="inviteLink(invite.jwt)">
              {{ invite.notes }}
            </td>
            <td class="text-center">
              {{ invite.redeemedAt ? "" : invite.expiresAt.substring(0, 10) }}
            </td>
            <td class="text-center">
              {{ invite.redeemedAt?.substring(0, 10) }}
              <br />
              {{ getTruncatedRedeemedBy(invite.redeemedBy) }}
              <br />
              <font-awesome
                v-if="invite.redeemedBy && !contactsRedeemed[invite.redeemedBy]"
                icon="plus"
                class="bg-green-600 text-white px-1 py-1 rounded-full cursor-pointer"
                @click="addNewContact(invite.redeemedBy, invite.notes)"
              />
            </td>
            <td>
              <font-awesome
                icon="trash-can"
                class="text-red-600 text-xl ml-2 mr-2 cursor-pointer"
                @click="deleteInvite(invite.inviteIdentifier, invite.notes)"
              />
            </td>
          </tr>
        </tbody>
      </table>
      <ContactNameDialog ref="contactNameDialog" />
    </div>
    <p v-else class="mt-6 text-center">No invites found.</p>
  </section>
</template>
<script lang="ts">
import axios from "axios";
import { Component, Vue } from "vue-facing-decorator";
import { useClipboard } from "@vueuse/core";
import { Router } from "vue-router";

import ContactNameDialog from "../components/ContactNameDialog.vue";
import QuickNav from "../components/QuickNav.vue";
import TopMessage from "../components/TopMessage.vue";
import InviteDialog from "../components/InviteDialog.vue";
import {
  APP_SERVER,
  AppString,
  NotificationIface,
  USE_DEXIE_DB,
} from "../constants/app";
import { db, retrieveSettingsForActiveAccount } from "../db/index";
import { Contact } from "../db/tables/contacts";
import * as databaseUtil from "../db/databaseUtil";
import { createInviteJwt, getHeaders } from "../libs/endorserServer";
import { PlatformServiceFactory } from "../services/PlatformServiceFactory";
import { logger } from "../utils/logger";

interface Invite {
  inviteIdentifier: string;
  expiresAt: string;
  jwt: string;
  notes: string;
  redeemedAt: string | null;
  redeemedBy: string | null;
}

@Component({
  components: { ContactNameDialog, QuickNav, TopMessage, InviteDialog },
})
export default class InviteOneView extends Vue {
  $notify!: (notification: NotificationIface, timeout?: number) => void;
  $router!: Router;

  invites: Invite[] = [];
  activeDid: string = "";
  apiServer: string = "";
  contactsRedeemed: { [key: string]: Contact } = {};
  isRegistered: boolean = false;
  showAppleWarning = false;

  async mounted() {
    try {
      let settings = await databaseUtil.retrieveSettingsForActiveAccount();
      if (USE_DEXIE_DB) {
        await db.open();
        settings = await retrieveSettingsForActiveAccount();
      }
      this.activeDid = settings.activeDid || "";
      this.apiServer = settings.apiServer || "";
      this.isRegistered = !!settings.isRegistered;

      const headers = await getHeaders(this.activeDid);
      const response = await axios.get(
        this.apiServer + "/api/userUtil/invite",
        { headers },
      );
      this.invites = response.data.data;

      const platformService = PlatformServiceFactory.getInstance();
      const queryResult = await platformService.dbQuery(
        "SELECT * FROM contacts",
      );
      let baseContacts = databaseUtil.mapQueryResultToValues(
        queryResult,
      ) as unknown as Contact[];
      if (USE_DEXIE_DB) {
        baseContacts = await db.contacts.toArray();
      }
      for (const invite of this.invites) {
        const contact = baseContacts.find(
          (contact) => contact.did === invite.redeemedBy,
        );
        if (contact && invite.redeemedBy) {
          this.contactsRedeemed[invite.redeemedBy] = contact;
        }
      }
    } catch (error) {
      logger.error("Error fetching invites:", error);
      this.$notify(
        {
          group: "alert",
          type: "danger",
          title: "Load Error",
          text: "Got an error loading your invites.",
        },
        5000,
      );
    }
  }

  getTruncatedInviteId(inviteId: string): string {
    if (inviteId.length <= 9) return inviteId;
    return `${inviteId.slice(0, 6)}...`;
  }

  getTruncatedRedeemedBy(redeemedBy: string | null): string {
    if (!redeemedBy) return "";
    if (this.contactsRedeemed[redeemedBy]) {
      return (
        this.contactsRedeemed[redeemedBy].name || AppString.NO_CONTACT_NAME
      );
    }
    if (redeemedBy.length <= 19) return redeemedBy;
    return `${redeemedBy.slice(0, 13)}...${redeemedBy.slice(-3)}`;
  }

  inviteLink(jwt: string): string {
    return APP_SERVER + "/deep-link/invite-one-accept/" + jwt;
  }

  copyInviteAndNotify(inviteId: string, jwt: string) {
    useClipboard().copy(this.inviteLink(jwt));
    this.$notify(
      {
        group: "alert",
        type: "success",
        title: "Copied",
        text: "Your clipboard now contains the link for invite " + inviteId,
      },
      5000,
    );
  }

  showInvite(inviteId: string, redeemed: boolean, expired: boolean) {
    let message = `Your clipboard now contains the invite ID ${inviteId}`;
    if (redeemed) {
      message += " (This invite has been used.)";
    } else if (expired) {
      message += " (This invite has expired.)";
    }
    useClipboard().copy(inviteId);
    this.$notify(
      {
        group: "alert",
        type: "success",
        title: "Copied",
        text: message,
      },
      5000,
    );
  }

  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  lookForErrorAndNotify(error: any, title: string, defaultMessage: string) {
    logger.error(title, "-", error);
    let message = defaultMessage;
    if (error.response && error.response.data && error.response.data.error) {
      if (error.response.data.error.message) {
        message = error.response.data.error.message;
      } else {
        message = error.response.data.error;
      }
    }
    this.$notify(
      {
        group: "alert",
        type: "danger",
        title: title,
        text: message,
      },
      5000,
    );
  }

  async createInvite() {
    const inviteIdentifier =
      Math.random().toString(36).substring(2) +
      Math.random().toString(36).substring(2) +
      Math.random().toString(36).substring(2);
    (this.$refs.inviteDialog as InviteDialog).open(
      inviteIdentifier,
      async (notes, expiresAt) => {
        try {
          const headers = await getHeaders(this.activeDid);
          if (!expiresAt) {
            throw {
              response: {
                data: { error: "You must select an expiration date." },
              },
            };
          }
          const expiresIn = (new Date(expiresAt).getTime() - Date.now()) / 1000;
          const inviteJwt = await createInviteJwt(
            this.activeDid,
            undefined,
            inviteIdentifier,
            expiresIn,
          );
          await axios.post(
            this.apiServer + "/api/userUtil/invite",
            { inviteJwt, notes, expiresAt },
            { headers },
          );
          const newInvite = {
            inviteIdentifier: inviteIdentifier,
            expiresAt: expiresAt,
            jwt: inviteJwt,
            notes: notes,
            redeemedAt: null,
            redeemedBy: null,
          };
          this.invites = [newInvite, ...this.invites];
          // eslint-disable-next-line @typescript-eslint/no-explicit-any
        } catch (error: any) {
          this.lookForErrorAndNotify(
            error,
            "Error Creating Invite",
            "Got an error creating your invite.",
          );
        }
      },
    );
  }

  async addNewContact(did: string, notes: string) {
    (this.$refs.contactNameDialog as ContactNameDialog).open(
      "To Whom Did You Send The Invite?",
      "Their name will be added to your contact list.",
      async (name) => {
        // the person obviously registered themselves and this user already granted visibility, so we just add them
        const contact = {
          did: did,
          name: name,
          registered: true,
        };
        const platformService = PlatformServiceFactory.getInstance();
        const columns = Object.keys(contact);
        const values = Object.values(contact);
        const placeholders = values.map(() => "?").join(", ");
        const sql = `INSERT INTO contacts (${columns.join(", ")}) VALUES (${placeholders})`;
        await platformService.dbExec(sql, values);
        if (USE_DEXIE_DB) {
          await db.contacts.add(contact);
        }

        this.contactsRedeemed[did] = contact;
        this.$notify(
          {
            group: "alert",
            type: "success",
            title: "Contact Added",
            text: `${name} has been added to your contacts.`,
          },
          3000,
        );
      },
      () => {},
      notes,
    );
  }

  deleteInvite(inviteId: string, notes: string) {
    this.$notify(
      {
        group: "modal",
        type: "confirm",
        title: "Delete Invite?",
        text: `Are you sure you want to erase the invite for "${notes}"? (There is no undo.)`,
        onYes: async () => {
          const headers = await getHeaders(this.activeDid);
          try {
            const result = await axios.delete(
              this.apiServer + "/api/userUtil/invite/" + inviteId,
              { headers },
            );
            if (result.status !== 204) {
              throw result.data;
            }
            this.invites = this.invites.filter(
              (invite) => invite.inviteIdentifier !== inviteId,
            );
            this.$notify(
              {
                group: "alert",
                type: "success",
                title: "Deleted",
                text: "Invite deleted.",
              },
              3000,
            );
          } catch (e) {
            this.lookForErrorAndNotify(
              e,
              "Error Deleting Invite",
              "Got an error deleting your invite.",
            );
          }
        },
      },
      -1,
    );
  }
}
</script>
