import * as admin from 'firebase-admin';
import * as firebase from 'firebase/app';
import { Timeslot, TimeslotJSON } from './timeslot';
import 'firebase/firestore';

/**
 * Type aliases so that we don't have to type out the whole type. We could try
 * importing these directly from the `@firebase/firestore-types` or the
 * `@google-cloud/firestore` packages, but that's not recommended.
 * @todo Perhaps figure out a way to **only** import the type defs we need.
 */
type DocumentData = firebase.firestore.DocumentData;
type DocumentSnapshot = firebase.firestore.DocumentSnapshot;
type DocumentReference = firebase.firestore.DocumentReference;
type SnapshotOptions = firebase.firestore.SnapshotOptions;
type AdminDocumentSnapshot = admin.firestore.DocumentSnapshot;
type AdminDocumentReference = admin.firestore.DocumentReference;

export type ApptVenueTypeAlias = 'bramble';
export type RoleAlias = 'tutor' | 'tutee' | 'mentor' | 'mentee';

export interface AttendeeInterface {
  id: string;
  roles: RoleAlias[];
}

/**
 * Right now, we only support one `ApptVenueType` via our
 * [Bramble]{@link https://about.bramble.io/api.html} integration.
 * @todo Add more supported venues like Zoom, Google Hangouts, or BigBlueButton.
 */
export interface ApptVenueInterface extends Record<string, any> {
  type: ApptVenueTypeAlias;
  url: string;
}

export interface ApptInterface {
  subjects: string[];
  attendees: AttendeeInterface[];
  time?: Timeslot;
  venues: ApptVenueInterface[];
  message?: string;
  ref?: DocumentReference | AdminDocumentReference;
  id?: string;
}

export interface ApptJSON {
  subjects: string[];
  attendees: AttendeeInterface[];
  time?: TimeslotJSON;
  message?: string;
  id?: string;
}

export class Appt implements ApptInterface {
  public message = '';

  public subjects: string[] = [];

  public attendees: AttendeeInterface[] = [];

  public venues: ApptVenueInterface[] = [];

  public ref?: DocumentReference | AdminDocumentReference;

  public time?: Timeslot;

  public id?: string;

  /**
   * Wrap your boring `Record`s with this class to ensure that they have all of
   * the needed `ApptInterface` values (we fill any missing values w/
   * the above specified defaults) **and** to gain access to a bunch of useful
   * conversion method, etc (e.g. `toString` actually makes sense now).
   * @todo Actually implement a useful `toString` method here.
   * @todo Perhaps add an explicit check to ensure that the given `val`'s type
   * matches the default value at `this[key]`'s type; and then only update the
   * default value if the types match.
   */
  public constructor(request: Partial<ApptInterface> = {}) {
    Object.entries(request).forEach(([key, val]: [string, any]) => {
      /* eslint-disable-next-line @typescript-eslint/no-unsafe-assignment */
      if (val && key in this) (this as Record<string, any>)[key] = val;
    });
  }

  public toJSON(): ApptJSON {
    const { time, ref, ...rest } = this;
    if (time) return { ...rest, time: time.toJSON() };
    return rest;
  }

  /**
   * Creates a new `Appt` object given the JSON representation of it.
   * @todo Convert Firestore document `path`s to `DocumentReference`s.
   */
  public static fromJSON(json: ApptJSON): Appt {
    const { time, ...rest } = json;
    if (time) return new Appt({ ...rest, time: Timeslot.fromJSON(time) });
    return new Appt(rest);
  }

  public toFirestore(): DocumentData {
    const { time, ref, id, ...rest } = this;
    if (time) return { ...rest, time: time.toFirestore() };
    return rest;
  }

  public static fromFirestore(
    snapshot: DocumentSnapshot | AdminDocumentSnapshot,
    options?: SnapshotOptions
  ): Appt {
    const apptData: DocumentData | undefined = snapshot.data(options);
    if (apptData) {
      const { time, ...rest } = apptData;
      return new Appt({
        ...rest,
        time: time ? Timeslot.fromFirestore(time) : undefined,
        ref: snapshot.ref,
        id: snapshot.id,
      });
    }
    console.warn(
      `[WARNING] Tried to create appt (${snapshot.ref.id}) from ` +
        'non-existent Firestore document.'
    );
    return new Appt();
  }
}
