<template>
  <div class="space-y-4">
    <!-- Loading State -->
    <div
      v-if="isLoading"
      class="mt-16 text-center text-4xl bg-slate-400 text-white w-14 py-2.5 rounded-full mx-auto"
    >
      <font-awesome icon="spinner" class="fa-spin-pulse" />
    </div>

    <!-- Members List -->

    <div v-else>
      <div class="text-center text-red-600 py-4">
        {{ decryptionErrorMessage() }}
      </div>

      <div v-if="missingMyself" class="py-4 text-red-600">
        You are not currently admitted by the organizer.
      </div>
      <div v-if="!firstName" class="py-4 text-red-600">
        Your name is not set, so others may not recognize you. Reload this page
        to set it.
      </div>

      <div>
        <span
          v-if="membersToShow().length > 0 && showOrganizerTools && isOrganizer"
          class="inline-flex items-center flex-wrap"
        >
          <span class="inline-flex items-center">
            &bull; Click
            <span
              class="mx-2 min-w-[24px] min-h-[24px] w-6 h-6 flex items-center justify-center rounded-full bg-blue-100 text-blue-600"
            >
              <font-awesome icon="plus" class="text-sm" />
            </span>
            /
            <span
              class="mx-2 min-w-[24px] min-h-[24px] w-6 h-6 flex items-center justify-center rounded-full bg-blue-100 text-blue-600"
            >
              <font-awesome icon="minus" class="text-sm" />
            </span>
            to add/remove them to/from the meeting.
          </span>
        </span>
      </div>
      <div>
        <span
          v-if="membersToShow().length > 0"
          class="inline-flex items-center"
        >
          &bull; Click
          <span
            class="mx-2 w-8 h-8 flex items-center justify-center rounded-full bg-green-100 text-green-600"
          >
            <font-awesome icon="circle-user" class="text-xl" />
          </span>
          to add them to your contacts.
        </span>
      </div>

      <div class="flex justify-center">
        <!-- 
        always have at least one refresh button even without members in case the organizer 
         changes the password 
         -->
        <button
          class="w-8 h-8 flex items-center justify-center rounded-full bg-blue-100 text-blue-600 hover:bg-blue-200 hover:text-blue-800 transition-colors"
          title="Refresh members list"
          @click="fetchMembers"
        >
          <font-awesome icon="rotate" :class="{ 'fa-spin': isLoading }" />
        </button>
      </div>
      <div
        v-for="member in membersToShow()"
        :key="member.member.memberId"
        class="mt-2 p-4 bg-gray-50 rounded-lg"
      >
        <div class="flex items-center justify-between">
          <div class="flex items-center">
            <h3 class="text-lg font-medium">{{ member.name }}</h3>
            <div
              v-if="!getContactFor(member.did) && member.did !== activeDid"
              class="flex justify-end"
            >
              <button
                class="ml-2 w-8 h-8 flex items-center justify-center rounded-full bg-green-100 text-green-600 hover:bg-green-200 hover:text-green-800 transition-colors"
                title="Add as contact"
                @click="addAsContact(member)"
              >
                <font-awesome icon="circle-user" class="text-xl" />
              </button>
            </div>
            <button
              v-if="member.did !== activeDid"
              class="ml-2 mb-2 w-6 h-6 flex items-center justify-center rounded-full bg-slate-100 text-slate-500 hover:bg-slate-200 hover:text-slate-800 transition-colors"
              title="Contact info"
              @click="
                informAboutAddingContact(
                  getContactFor(member.did) !== undefined,
                )
              "
            >
              <font-awesome icon="circle-info" class="text-base" />
            </button>
          </div>
          <div class="flex">
            <span
              v-if="
                showOrganizerTools && isOrganizer && member.did !== activeDid
              "
              class="flex items-center"
            >
              <button
                class="mr-2 w-6 h-6 flex items-center justify-center rounded-full bg-blue-100 text-blue-600 hover:bg-blue-200 hover:text-blue-800 transition-colors"
                :title="
                  member.member.admitted ? 'Remove member' : 'Admit member'
                "
                @click="checkWhetherContactBeforeAdmitting(member)"
              >
                <font-awesome
                  :icon="member.member.admitted ? 'minus' : 'plus'"
                  class="text-sm"
                />
              </button>
              <button
                class="mr-2 mb-2 w-6 h-6 flex items-center justify-center rounded-full bg-slate-100 text-slate-500 hover:bg-slate-200 hover:text-slate-800 transition-colors"
                title="Admission info"
                @click="informAboutAdmission()"
              >
                <font-awesome icon="circle-info" class="text-base" />
              </button>
            </span>
          </div>
        </div>
        <p class="text-sm text-gray-600 truncate">
          {{ member.did }}
        </p>
      </div>
      <div v-if="membersToShow().length > 0" class="flex justify-center mt-4">
        <button
          class="w-8 h-8 flex items-center justify-center rounded-full bg-blue-100 text-blue-600 hover:bg-blue-200 hover:text-blue-800 transition-colors"
          title="Refresh members list"
          @click="fetchMembers"
        >
          <font-awesome icon="rotate" :class="{ 'fa-spin': isLoading }" />
        </button>
      </div>

      <p v-if="members.length === 0" class="text-gray-500 py-4">
        No members have joined this meeting yet
      </p>
    </div>
  </div>
</template>

<script lang="ts">
import { Component, Vue, Prop } from "vue-facing-decorator";

import {
  logConsoleAndDb,
  retrieveSettingsForActiveAccount,
  db,
} from "../db/index";
import {
  errorStringForLog,
  getHeaders,
  register,
  serverMessageForUser,
} from "../libs/endorserServer";
import { decryptMessage } from "../libs/crypto";
import { Contact } from "../db/tables/contacts";
import * as databaseUtil from "../db/databaseUtil";
import * as libsUtil from "../libs/util";
import { NotificationIface, USE_DEXIE_DB } from "../constants/app";
import { PlatformServiceFactory } from "@/services/PlatformServiceFactory";

interface Member {
  admitted: boolean;
  content: string;
  memberId: number;
}

interface DecryptedMember {
  member: Member;
  name: string;
  did: string;
  isRegistered: boolean;
}

@Component
export default class MembersList extends Vue {
  $notify!: (notification: NotificationIface, timeout?: number) => void;

  libsUtil = libsUtil;

  @Prop({ required: true }) password!: string;
  @Prop({ default: false }) showOrganizerTools!: boolean;

  decryptedMembers: DecryptedMember[] = [];
  firstName = "";
  isLoading = true;
  isOrganizer = false;
  members: Member[] = [];
  missingPassword = false;
  missingMyself = false;
  activeDid = "";
  apiServer = "";
  contacts: Array<Contact> = [];

  async created() {
    let settings = await databaseUtil.retrieveSettingsForActiveAccount();
    if (USE_DEXIE_DB) {
      settings = await retrieveSettingsForActiveAccount();
    }
    this.activeDid = settings.activeDid || "";
    this.apiServer = settings.apiServer || "";
    this.firstName = settings.firstName || "";
    await this.fetchMembers();
    await this.loadContacts();
  }

  async fetchMembers() {
    try {
      this.isLoading = true;
      const headers = await getHeaders(this.activeDid);
      const response = await this.axios.get(
        `${this.apiServer}/api/partner/groupOnboardMembers`,
        { headers },
      );

      if (response.data && response.data.data) {
        this.members = response.data.data;
        await this.decryptMemberContents();
      }
    } catch (error) {
      logConsoleAndDb(
        "Error fetching members: " + errorStringForLog(error),
        true,
      );
      this.$emit(
        "error",
        serverMessageForUser(error) || "Failed to fetch members.",
      );
    } finally {
      this.isLoading = false;
    }
  }

  async decryptMemberContents() {
    this.decryptedMembers = [];

    if (!this.password) {
      this.missingPassword = true;
      return;
    }

    let isFirstEntry = true,
      foundMyself = false;
    for (const member of this.members) {
      try {
        const decryptedContent = await decryptMessage(
          member.content,
          this.password,
        );
        const content = JSON.parse(decryptedContent);

        this.decryptedMembers.push({
          member: member,
          name: content.name,
          did: content.did,
          isRegistered: !!content.isRegistered,
        });
        if (isFirstEntry && content.did === this.activeDid) {
          this.isOrganizer = true;
        }
        if (content.did === this.activeDid) {
          foundMyself = true;
        }
      } catch (error) {
        // do nothing, relying on the count of members to determine if there was an error
      }
      isFirstEntry = false;
    }
    this.missingMyself = !foundMyself;
  }

  decryptionErrorMessage(): string {
    if (this.isOrganizer) {
      if (this.decryptedMembers.length < this.members.length) {
        return "Some members have data that cannot be decrypted with that password.";
      } else {
        // the lists must be equal
        return "";
      }
    } else {
      // non-organizers should only see problems if the first (organizer) member is not decrypted
      if (
        this.decryptedMembers.length === 0 ||
        this.decryptedMembers[0].member.memberId !== this.members[0].memberId
      ) {
        return "Your password is not the same as the organizer. Retry or have them check their password.";
      } else {
        // the first (organizer) member was decrypted OK
        return "";
      }
    }
  }

  membersToShow(): DecryptedMember[] {
    if (this.isOrganizer) {
      if (this.showOrganizerTools) {
        return this.decryptedMembers;
      } else {
        return this.decryptedMembers.filter(
          (member: DecryptedMember) => member.member.admitted,
        );
      }
    }
    // non-organizers only get visible members from server
    return this.decryptedMembers;
  }

  informAboutAdmission() {
    this.$notify(
      {
        group: "alert",
        type: "info",
        title: "Admission info",
        text: "This is to register people in Time Safari and to admit them to the meeting. A '+' symbol means they are not yet admitted and you can register and admit them. A '-' means you can remove them, but they will stay registered.",
      },
      10000,
    );
  }

  informAboutAddingContact(contactImportedAlready: boolean) {
    if (contactImportedAlready) {
      this.$notify(
        {
          group: "alert",
          type: "info",
          title: "Contact Exists",
          text: "They are in your contacts. To remove them, use the contacts page.",
        },
        10000,
      );
    } else {
      this.$notify(
        {
          group: "alert",
          type: "info",
          title: "Contact Available",
          text: "This is to add them to your contacts. To remove them later, use the contacts page.",
        },
        10000,
      );
    }
  }

  async loadContacts() {
    const platformService = PlatformServiceFactory.getInstance();
    const result = await platformService.dbQuery("SELECT * FROM contacts");
    if (result) {
      this.contacts = databaseUtil.mapQueryResultToValues(
        result,
      ) as unknown as Contact[];
    }
    if (USE_DEXIE_DB) {
      this.contacts = await db.contacts.toArray();
    }
  }

  getContactFor(did: string): Contact | undefined {
    return this.contacts.find((contact) => contact.did === did);
  }

  checkWhetherContactBeforeAdmitting(decrMember: DecryptedMember) {
    const contact = this.getContactFor(decrMember.did);
    if (!decrMember.member.admitted && !contact) {
      // If not a contact, show confirmation dialog
      this.$notify(
        {
          group: "modal",
          type: "confirm",
          title: "Add as Contact First?",
          text: "This person is not in your contacts. Would you like to add them as a contact first?",
          yesText: "Add as Contact",
          noText: "Skip Adding Contact",
          onYes: async () => {
            await this.addAsContact(decrMember);
            // After adding as contact, proceed with admission
            await this.toggleAdmission(decrMember);
          },
          onNo: async () => {
            // If they choose not to add as contact, show second confirmation
            this.$notify(
              {
                group: "modal",
                type: "confirm",
                title: "Continue Without Adding?",
                text: "Are you sure you want to proceed with admission? If they are not a contact, you will not know their name after this meeting.",
                yesText: "Continue",
                onYes: async () => {
                  await this.toggleAdmission(decrMember);
                },
                onCancel: async () => {
                  // Do nothing, effectively canceling the operation
                },
              },
              -1,
            );
          },
        },
        -1,
      );
    } else {
      // If already a contact, proceed directly with admission
      this.toggleAdmission(decrMember);
    }
  }

  async toggleAdmission(decrMember: DecryptedMember) {
    try {
      const headers = await getHeaders(this.activeDid);
      await this.axios.put(
        `${this.apiServer}/api/partner/groupOnboardMember/${decrMember.member.memberId}`,
        { admitted: !decrMember.member.admitted },
        { headers },
      );
      // Update local state
      decrMember.member.admitted = !decrMember.member.admitted;

      const oldContact = this.getContactFor(decrMember.did);
      // if admitted, now register that user if they are not registered
      if (
        decrMember.member.admitted &&
        !decrMember.isRegistered &&
        !oldContact?.registered
      ) {
        const contactOldOrNew: Contact = oldContact || {
          did: decrMember.did,
          name: decrMember.name,
        };
        try {
          const result = await register(
            this.activeDid,
            this.apiServer,
            this.axios,
            contactOldOrNew,
          );
          if (result.success) {
            decrMember.isRegistered = true;
            if (oldContact) {
              const platformService = PlatformServiceFactory.getInstance();
              await platformService.dbExec(
                "UPDATE contacts SET registered = ? WHERE did = ?",
                [true, decrMember.did],
              );
              if (USE_DEXIE_DB) {
                await db.contacts.update(decrMember.did, { registered: true });
              }
              oldContact.registered = true;
            }
            this.$notify(
              {
                group: "alert",
                type: "success",
                title: "Registered",
                text: "Besides being admitted, they were also registered.",
              },
              3000,
            );
          } else {
            throw result;
          }
          // eslint-disable-next-line @typescript-eslint/no-explicit-any
        } catch (error: any) {
          // registration failure is likely explained by a message from the server
          const additionalInfo =
            serverMessageForUser(error) || error?.error || "";
          this.$notify(
            {
              group: "alert",
              type: "warning",
              title: "Registration failed",
              text:
                "They were admitted to the meeting. However, registration failed. You can register them from the contacts screen. " +
                additionalInfo,
            },
            12000,
          );
        }
      }
    } catch (error) {
      logConsoleAndDb(
        "Error toggling admission: " + errorStringForLog(error),
        true,
      );
      this.$emit(
        "error",
        serverMessageForUser(error) ||
          "Failed to update member admission status.",
      );
    }
  }

  async addAsContact(member: DecryptedMember) {
    try {
      const newContact = {
        did: member.did,
        name: member.name,
      };

      const platformService = PlatformServiceFactory.getInstance();
      await platformService.dbExec(
        "INSERT INTO contacts (did, name) VALUES (?, ?)",
        [member.did, member.name],
      );
      if (USE_DEXIE_DB) {
        await db.contacts.add(newContact);
      }
      this.contacts.push(newContact);

      this.$notify(
        {
          group: "alert",
          type: "success",
          title: "Contact Added",
          text: "They were added to your contacts.",
        },
        3000,
      );
    } catch (err) {
      logConsoleAndDb("Error adding contact: " + errorStringForLog(err), true);
      let message = "An error prevented adding this contact.";
      if (err instanceof Error && err.message?.indexOf("already exists") > -1) {
        message = "This person is already in your contact list.";
      }
      this.$notify(
        {
          group: "alert",
          type: "danger",
          title: "Contact Not Added",
          text: message,
        },
        5000,
      );
    }
  }
}
</script>
