import Head from "next/head";
import prisma from "@lib/prisma";
import { useSession } from "next-auth/client";
import {
  EventTypeCreateInput,
  ScheduleCreateInput,
  User,
  UserUpdateInput,
  EventType,
  Schedule,
} from "@prisma/client";
import { NextPageContext } from "next";
import React, { useState, useEffect, useRef } from "react";
import { validJson } from "@lib/jsonUtils";
import TimezoneSelect from "react-timezone-select";
import Text from "@components/ui/Text";
import ErrorAlert from "@components/ui/alerts/Error";
import dayjs from "dayjs";
import utc from "dayjs/plugin/utc";
import timezone from "dayjs/plugin/timezone";
dayjs.extend(utc);
dayjs.extend(timezone);
import AddCalDavIntegration, {
  ADD_CALDAV_INTEGRATION_FORM_TITLE,
} from "@lib/integrations/CalDav/components/AddCalDavIntegration";
import { Dialog, DialogClose, DialogContent, DialogHeader } from "@components/Dialog";
import SchedulerForm, { SCHEDULE_FORM_ID } from "@components/ui/Schedule/Schedule";
import { useRouter } from "next/router";
import { Integration } from "pages/integrations";
import { AddCalDavIntegrationRequest } from "../lib/integrations/CalDav/components/AddCalDavIntegration";
import classnames from "classnames";
import { ArrowRightIcon } from "@heroicons/react/outline";
import { getSession } from "@lib/auth";

const DEFAULT_EVENT_TYPES = [
  {
    title: "15 Min Meeting",
    slug: "15min",
    length: 15,
  },
  {
    title: "30 Min Meeting",
    slug: "30min",
    length: 30,
  },
  {
    title: "Secret Meeting",
    slug: "secret",
    length: 15,
    hidden: true,
  },
];

type OnboardingProps = {
  user: User;
  integrations?: Record<string, string>[];
  eventTypes?: EventType[];
  schedules?: Schedule[];
};

export default function Onboarding(props: OnboardingProps) {
  const router = useRouter();

  const [enteredName, setEnteredName] = React.useState();
  const Sess = useSession();
  const [ready, setReady] = useState(false);
  const [error, setError] = useState(null);

  const updateUser = async (data: UserUpdateInput) => {
    const res = await fetch(`/api/user/${props.user.id}`, {
      method: "PATCH",
      body: JSON.stringify({ data: { ...data } }),
      headers: {
        "Content-Type": "application/json",
      },
    });

    if (!res.ok) {
      throw new Error((await res.json()).message);
    }
    const responseData = await res.json();
    return responseData.data;
  };

  const createEventType = async (data: EventTypeCreateInput) => {
    const res = await fetch(`/api/availability/eventtype`, {
      method: "POST",
      body: JSON.stringify(data),
      headers: {
        "Content-Type": "application/json",
      },
    });

    if (!res.ok) {
      throw new Error((await res.json()).message);
    }
    const responseData = await res.json();
    return responseData.data;
  };

  const createSchedule = async (data: ScheduleCreateInput) => {
    const res = await fetch(`/api/schedule`, {
      method: "POST",
      body: JSON.stringify({ data: { ...data } }),
      headers: {
        "Content-Type": "application/json",
      },
    });

    if (!res.ok) {
      throw new Error((await res.json()).message);
    }
    const responseData = await res.json();
    return responseData.data;
  };

  const integrationHandler = (type: string) => {
    if (type === "caldav_calendar") {
      setAddCalDavError(null);
      setIsAddCalDavIntegrationDialogOpen(true);
      return;
    }

    fetch("/api/integrations/" + type.replace("_", "") + "/add")
      .then((response) => response.json())
      .then((data) => {
        window.location.href = data.url;
      });
  };

  /** Internal Components */
  const IntegrationGridListItem = ({ integration }: { integration: Integration }) => {
    if (!integration || !integration.installed) {
      return null;
    }

    return (
      <li onClick={() => integrationHandler(integration.type)} key={integration.type} className="flex py-4">
        <div className="w-1/12 mr-4 pt-2">
          <img className="h-8 w-8 mr-2" src={integration.imageSrc} alt={integration.title} />
        </div>
        <div className="w-10/12">
          <Text className="text-gray-800 font-medium">{integration.title}</Text>
          <Text className="text-gray-400" variant="subtitle">
            {integration.description}
          </Text>
        </div>
        <div className="w-2/12 text-right pt-2">
          <button
            onClick={() => integrationHandler(integration.type)}
            className="font-medium text-neutral-900 hover:text-neutral-500 border px-4 py-2 border-gray-200 rounded-sm">
            Connect
          </button>
        </div>
      </li>
    );
  };
  /** End Internal Components */

  /** Name */
  const nameRef = useRef(null);
  const bioRef = useRef(null);
  /** End Name */
  /** TimeZone */
  const [selectedTimeZone, setSelectedTimeZone] = useState({
    value: props.user.timeZone ?? dayjs.tz.guess(),
    label: null,
  });
  const currentTime = React.useMemo(() => {
    return dayjs().tz(selectedTimeZone.value).format("H:mm A");
  }, [selectedTimeZone]);
  /** End TimeZone */

  /** CalDav Form */
  const addCalDavIntegrationRef = useRef<HTMLFormElement>(null);
  const [isAddCalDavIntegrationDialogOpen, setIsAddCalDavIntegrationDialogOpen] = useState(false);
  const [addCalDavError, setAddCalDavError] = useState<{ message: string } | null>(null);

  const handleAddCalDavIntegration = async ({ url, username, password }: AddCalDavIntegrationRequest) => {
    const requestBody = JSON.stringify({
      url,
      username,
      password,
    });

    return await fetch("/api/integrations/caldav/add", {
      method: "POST",
      body: requestBody,
      headers: {
        "Content-Type": "application/json",
      },
    });
  };

  const handleAddCalDavIntegrationSaveButtonPress = async () => {
    const form = addCalDavIntegrationRef.current.elements;
    const url = form.url.value;
    const password = form.password.value;
    const username = form.username.value;

    try {
      setAddCalDavError(null);
      const addCalDavIntegrationResponse = await handleAddCalDavIntegration({ username, password, url });
      if (addCalDavIntegrationResponse.ok) {
        setIsAddCalDavIntegrationDialogOpen(false);
        incrementStep();
      } else {
        const j = await addCalDavIntegrationResponse.json();
        setAddCalDavError({ message: j.message });
      }
    } catch (reason) {
      console.error(reason);
    }
  };

  const ConnectCalDavServerDialog = () => {
    return (
      <Dialog
        open={isAddCalDavIntegrationDialogOpen}
        onOpenChange={(isOpen) => setIsAddCalDavIntegrationDialogOpen(isOpen)}>
        <DialogContent>
          <DialogHeader
            title="Connect to CalDav Server"
            subtitle="Your credentials will be stored and encrypted."
          />
          <div className="my-4">
            {addCalDavError && (
              <p className="text-red-700 text-sm">
                <span className="font-bold">Error: </span>
                {addCalDavError.message}
              </p>
            )}
            <AddCalDavIntegration
              ref={addCalDavIntegrationRef}
              onSubmit={handleAddCalDavIntegrationSaveButtonPress}
            />
          </div>
          <div className="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
            <button
              type="submit"
              form={ADD_CALDAV_INTEGRATION_FORM_TITLE}
              className="flex justify-center py-2 px-4 border border-transparent rounded-sm shadow-sm text-sm font-medium text-white bg-neutral-900 hover:bg-neutral-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-neutral-900">
              Save
            </button>
            <DialogClose
              onClick={() => {
                setIsAddCalDavIntegrationDialogOpen(false);
              }}
              as="button"
              className="btn btn-white mx-2">
              Cancel
            </DialogClose>
          </div>
        </DialogContent>
      </Dialog>
    );
  };
  /**End CalDav Form */

  /** Onboarding Steps */
  const [currentStep, setCurrentStep] = useState(0);
  const detectStep = () => {
    let step = 0;
    const hasSetUserNameOrTimeZone = props.user.name && props.user.timeZone;
    if (hasSetUserNameOrTimeZone) {
      step = 1;
    }

    const hasConfigureCalendar = props.integrations.some((integration) => integration.credential != null);
    if (hasConfigureCalendar) {
      step = 2;
    }

    const hasSchedules = props.schedules && props.schedules.length > 0;
    if (hasSchedules) {
      step = 3;
    }

    setCurrentStep(step);
  };

  const handleConfirmStep = async () => {
    try {
      if (
        steps[currentStep] &&
        steps[currentStep]?.onComplete &&
        typeof steps[currentStep]?.onComplete === "function"
      ) {
        await steps[currentStep].onComplete();
      }
      incrementStep();
    } catch (error) {
      console.log("handleConfirmStep", error);
      setError(error);
    }
  };

  const handleSkipStep = () => {
    incrementStep();
  };

  const incrementStep = () => {
    const nextStep = currentStep + 1;

    if (nextStep >= steps.length) {
      completeOnboarding();
      return;
    }
    setCurrentStep(nextStep);
  };

  const decrementStep = () => {
    const previous = currentStep - 1;

    if (previous < 0) {
      return;
    }
    setCurrentStep(previous);
  };

  const goToStep = (step: number) => {
    setCurrentStep(step);
  };

  /**
   * Complete Onboarding finalizes the onboarding flow for a new user.
   *
   * Here, 3 event types are pre-created for the user as well.
   * Set to the availability the user enter during the onboarding.
   *
   * If a user skips through the Onboarding flow,
   * then the default availability is applied.
   */
  const completeOnboarding = async () => {
    if (!props.eventTypes || props.eventTypes.length === 0) {
      Promise.all(
        DEFAULT_EVENT_TYPES.map(async (event) => {
          return await createEventType(event);
        })
      );
    }
    await updateUser({
      completedOnboarding: true,
    });

    router.push("/event-types");
  };

  const steps = [
    {
      id: "welcome",
      title: "Welcome to Calendso",
      description:
        "Tell us what to call you and let us know what timezone you’re in. You’ll be able to edit this later.",
      Component: (
        <form className="sm:mx-auto sm:w-full sm:max-w-md">
          <section className="space-y-4">
            <fieldset>
              <label htmlFor="name" className="block text-sm font-medium text-gray-700">
                Full name
              </label>
              <input
                ref={nameRef}
                type="text"
                name="name"
                id="name"
                autoComplete="given-name"
                placeholder="Your name"
                defaultValue={props.user.name}
                required
                className="mt-1 block w-full border border-gray-300 rounded-sm shadow-sm py-2 px-3 focus:outline-none focus:ring-neutral-500 focus:border-neutral-500 sm:text-sm"
              />
            </fieldset>

            <fieldset>
              <section className="flex justify-between">
                <label htmlFor="timeZone" className="block text-sm font-medium text-gray-700">
                  Timezone
                </label>
                <Text variant="caption">
                  Current time:&nbsp;
                  <span className="text-black">{currentTime}</span>
                </Text>
              </section>
              <TimezoneSelect
                id="timeZone"
                value={selectedTimeZone}
                onChange={setSelectedTimeZone}
                className="shadow-sm focus:ring-blue-500 focus:border-blue-500 mt-1 block w-full sm:text-sm border-gray-300 rounded-md"
              />
            </fieldset>
          </section>
        </form>
      ),
      hideConfirm: false,
      confirmText: "Continue",
      showCancel: true,
      cancelText: "Set up later",
      onComplete: async () => {
        try {
          await updateUser({
            name: nameRef.current.value,
            timeZone: selectedTimeZone.value,
          });
          setEnteredName(nameRef.current.value);
        } catch (error) {
          setError(error);
        }
      },
    },
    {
      id: "connect-calendar",
      title: "Connect your calendar",
      description:
        "Connect your calendar to automatically check for busy times and new events as they’re scheduled.",
      Component: (
        <ul className="divide-y divide-gray-200 sm:mx-auto sm:w-full sm:max-w-md">
          {props.integrations.map((integration) => {
            return <IntegrationGridListItem key={integration.type} integration={integration} />;
          })}
        </ul>
      ),
      hideConfirm: true,
      confirmText: "Continue",
      showCancel: true,
      cancelText: "Continue without calendar",
    },
    {
      id: "set-availability",
      title: "Set your availability",
      description:
        "Define ranges of time when you are available on a recurring basis. You can create more of these later and assign them to different calendars.",
      Component: (
        <>
          <section className="bg-white dark:bg-opacity-5 text-black dark:text-white mx-auto max-w-lg">
            <SchedulerForm
              onSubmit={async (data) => {
                try {
                  await createSchedule({
                    freeBusyTimes: data,
                  });
                  handleConfirmStep();
                } catch (error) {
                  setError(error);
                }
              }}
            />
          </section>
          <footer className="py-6 sm:mx-auto sm:w-full sm:max-w-md flex flex-col space-y-6">
            <button
              type="submit"
              form={SCHEDULE_FORM_ID}
              className="w-full btn btn-primary text-center justify-center space-x-2">
              <Text variant="subtitle" className="text-white">
                Continue
              </Text>
              <ArrowRightIcon className="text-white h-4 w-4" />
            </button>
          </footer>
        </>
      ),
      hideConfirm: true,
      showCancel: false,
    },
    {
      id: "profile",
      title: "Nearly there",
      description:
        "Last thing, a brief description about you and a photo really help you get bookings and let people know who they’re booking with.",
      Component: (
        <form className="sm:mx-auto sm:w-full sm:max-w-md" id="ONBOARDING_STEP_4">
          <section className="space-y-4">
            <fieldset>
              <label htmlFor="name" className="block text-sm font-medium text-gray-700">
                Full name
              </label>
              <input
                ref={nameRef}
                type="text"
                name="name"
                id="name"
                autoComplete="given-name"
                placeholder="Your name"
                defaultValue={props.user.name || enteredName}
                required
                className="mt-1 block w-full border border-gray-300 rounded-sm shadow-sm py-2 px-3 focus:outline-none focus:ring-neutral-500 focus:border-neutral-500 sm:text-sm"
              />
            </fieldset>
            <fieldset>
              <label htmlFor="bio" className="block text-sm font-medium text-gray-700">
                About
              </label>
              <input
                ref={bioRef}
                type="text"
                name="bio"
                id="bio"
                required
                className="mt-1 block w-full border border-gray-300 rounded-sm shadow-sm py-2 px-3 focus:outline-none focus:ring-neutral-500 focus:border-neutral-500 sm:text-sm"
                defaultValue={props.user.bio}
              />
              <Text variant="caption">
                A few sentences about yourself. This will appear on your personal url page.
              </Text>
            </fieldset>
          </section>
        </form>
      ),
      hideConfirm: false,
      confirmText: "Finish",
      showCancel: true,
      cancelText: "Set up later",
      onComplete: async () => {
        try {
          await updateUser({
            bio: bioRef.current.value,
          });
        } catch (error) {
          setError(error);
        }
      },
    },
  ];
  /** End Onboarding Steps */

  useEffect(() => {
    detectStep();
    setReady(true);
  }, []);

  if (Sess[1] || !ready) {
    return <div className="loader"></div>;
  }

  return (
    <div className="bg-black min-h-screen">
      <Head>
        <title>Calendso - Getting Started</title>
        <link rel="icon" href="/favicon.ico" />
      </Head>

      <div className="mx-auto py-24 px-4">
        <article>
          <section className="sm:mx-auto sm:w-full sm:max-w-md space-y-4">
            <header className="">
              <Text className="text-white" variant="largetitle">
                {steps[currentStep].title}
              </Text>
              <Text className="text-white" variant="subtitle">
                {steps[currentStep].description}
              </Text>
            </header>
            <section className="space-y-2">
              <Text variant="footnote">
                Step {currentStep + 1} of {steps.length}
              </Text>

              {error && <ErrorAlert {...error} />}

              <section className="w-full space-x-2 flex">
                {steps.map((s, index) => {
                  return index <= currentStep ? (
                    <div
                      key={`step-${index}`}
                      onClick={() => goToStep(index)}
                      className={classnames(
                        "h-1 bg-white w-1/4",
                        index < currentStep ? "cursor-pointer" : ""
                      )}></div>
                  ) : (
                    <div key={`step-${index}`} className="h-1 bg-white bg-opacity-25 w-1/4"></div>
                  );
                })}
              </section>
            </section>
          </section>
          <section className="py-6 mt-10 mx-auto max-w-xl bg-white p-10 rounded-sm">
            {steps[currentStep].Component}

            {!steps[currentStep].hideConfirm && (
              <footer className="py-6 sm:mx-auto sm:w-full sm:max-w-md flex flex-col space-y-6 mt-8">
                <button
                  onClick={handleConfirmStep}
                  type="button"
                  className="w-full btn btn-primary text-center justify-center space-x-2">
                  <Text variant="subtitle" className="text-white">
                    {steps[currentStep].confirmText}
                  </Text>
                  <ArrowRightIcon className="text-white h-4 w-4" />
                </button>
              </footer>
            )}
          </section>
          <section className="py-6 mt-8 mx-auto max-w-xl">
            <div className="flex justify-between">
              <button onClick={decrementStep}>
                <Text variant="caption">Prev Step</Text>
              </button>
              <button onClick={handleSkipStep}>
                <Text variant="caption">Skip Step</Text>
              </button>
            </div>
          </section>
        </article>
      </div>
      <ConnectCalDavServerDialog />
    </div>
  );
}

export async function getServerSideProps(context: NextPageContext) {
  const session = await getSession(context);

  let integrations = [];
  let credentials = [];
  let eventTypes = [];
  let schedules = [];
  if (!session?.user?.id) {
    return {
      redirect: {
        permanent: false,
        destination: "/auth/login",
      } as const,
    };
  }
  const user = await prisma.user.findFirst({
    where: {
      id: session.user.id,
    },
    select: {
      id: true,
      startTime: true,
      endTime: true,
      username: true,
      name: true,
      email: true,
      bio: true,
      avatar: true,
      timeZone: true,
      completedOnboarding: true,
    },
  });
  if (!user) {
    throw new Error(`Signed in as ${session.user.id} but cannot be found in db`);
  }

  if (user.completedOnboarding) {
    return {
      redirect: {
        permanent: false,
        destination: "/event-types",
      },
    };
  }

  credentials = await prisma.credential.findMany({
    where: {
      userId: user.id,
    },
    select: {
      id: true,
      type: true,
      key: true,
    },
  });

  integrations = [
    {
      installed: !!(process.env.GOOGLE_API_CREDENTIALS && validJson(process.env.GOOGLE_API_CREDENTIALS)),
      credential: credentials.find((integration) => integration.type === "google_calendar") || null,
      type: "google_calendar",
      title: "Google Calendar",
      imageSrc: "integrations/google-calendar.svg",
      description: "Gmail, G Suite",
    },
    {
      installed: !!(process.env.MS_GRAPH_CLIENT_ID && process.env.MS_GRAPH_CLIENT_SECRET),
      credential: credentials.find((integration) => integration.type === "office365_calendar") || null,
      type: "office365_calendar",
      title: "Office 365 Calendar",
      imageSrc: "integrations/outlook.svg",
      description: "Office 365, Outlook.com, live.com, or hotmail calendar",
    },
    {
      installed: !!(process.env.ZOOM_CLIENT_ID && process.env.ZOOM_CLIENT_SECRET),
      credential: credentials.find((integration) => integration.type === "zoom_video") || null,
      type: "zoom_video",
      title: "Zoom",
      imageSrc: "integrations/zoom.svg",
      description: "Video Conferencing",
    },
    {
      installed: true,
      credential: credentials.find((integration) => integration.type === "caldav_calendar") || null,
      type: "caldav_calendar",
      title: "Caldav",
      imageSrc: "integrations/caldav.svg",
      description: "CalDav Server",
    },
  ];

  eventTypes = await prisma.eventType.findMany({
    where: {
      userId: user.id,
    },
    select: {
      id: true,
      title: true,
      slug: true,
      description: true,
      length: true,
      hidden: true,
    },
  });

  schedules = await prisma.schedule.findMany({
    where: {
      userId: user.id,
    },
    select: {
      id: true,
    },
  });

  return {
    props: {
      user,
      integrations,
      eventTypes,
      schedules,
    },
  };
}
