import { state, property } from "lit/decorators.js";
import { msg, localized } from "@lit/localize";
import { createMachine, interpret, assign } from "@xstate/fsm";

import type { ViewState } from "../utils/APIRouter";
import LiteElement, { html } from "../utils/LiteElement";
import type { LoggedInEventDetail } from "../utils/AuthService";
import AuthService from "../utils/AuthService";
import { DASHBOARD_ROUTE } from "../routes";

type FormContext = {
  successMessage?: string;
  serverError?: string;
};
type FormSuccessEvent = {
  type: "SUCCESS";
  detail: {
    successMessage?: FormContext["successMessage"];
  };
};
type FormErrorEvent = {
  type: "ERROR";
  detail: {
    serverError?: FormContext["serverError"];
  };
};
type FormEvent =
  | { type: "SHOW_SIGN_IN_WITH_PASSWORD" }
  | { type: "SHOW_FORGOT_PASSWORD" }
  | { type: "SUBMIT" }
  | FormSuccessEvent
  | FormErrorEvent;

type FormTypestate =
  | {
      value: "signIn";
      context: FormContext;
    }
  | {
      value: "signingIn";
      context: FormContext;
    }
  | {
      value: "signIn";
      context: FormContext;
    }
  | {
      value: "forgotPassword";
      context: FormContext;
    }
  | {
      value: "submittingForgotPassword";
      context: FormContext;
    };

const initialContext = {};

const machine = createMachine<FormContext, FormEvent, FormTypestate>(
  {
    id: "loginForm",
    initial: "signIn",
    context: initialContext,
    states: {
      ["signIn"]: {
        on: {
          SHOW_FORGOT_PASSWORD: {
            target: "forgotPassword",
            actions: "reset",
          },
          SUBMIT: {
            target: "signingIn",
            actions: "reset",
          },
        },
      },
      ["signingIn"]: {
        on: {
          SUCCESS: "signedIn",
          ERROR: {
            target: "signIn",
            actions: "setError",
          },
        },
      },
      ["forgotPassword"]: {
        on: {
          SHOW_SIGN_IN_WITH_PASSWORD: {
            target: "signIn",
            actions: "reset",
          },
          SUBMIT: {
            target: "submittingForgotPassword",
            actions: "reset",
          },
        },
      },
      ["submittingForgotPassword"]: {
        on: {
          SUCCESS: {
            target: "signIn",
            actions: "setSuccessMessage",
          },
          ERROR: {
            target: "forgotPassword",
            actions: "setError",
          },
        },
      },
    },
  },
  {
    actions: {
      reset: assign(() => initialContext),
      setSuccessMessage: assign((context, event) => ({
        ...context,
        ...(event as FormSuccessEvent).detail,
      })),
      setError: assign((context, event) => ({
        ...context,
        ...(event as FormErrorEvent).detail,
      })),
    },
  }
);

@localized()
export class LogInPage extends LiteElement {
  @property({ type: Object })
  viewState!: ViewState;

  @property({ type: String })
  redirectUrl: string = DASHBOARD_ROUTE;

  private formStateService = interpret(machine);

  @state()
  private formState = machine.initialState;

  firstUpdated() {
    this.formStateService.subscribe((state) => {
      this.formState = state;
    });

    this.formStateService.start();
  }

  disconnectedCallback() {
    this.formStateService.stop();
    super.disconnectedCallback();
  }

  async updated(changedProperties: any) {
    if (changedProperties.get("viewState")) {
      await this.updateComplete;

      this.syncFormStateView();
    }
  }

  render() {
    let form, link, successMessage;

    if (
      this.formState.value === "forgotPassword" ||
      this.formState.value === "submittingForgotPassword"
    ) {
      form = this.renderForgotPasswordForm();
      link = html`
        <a
          class="text-sm text-gray-400 hover:text-gray-500"
          href="/log-in"
          @click=${this.navLink}
          >${msg("Sign in with password")}</a
        >
      `;
    } else {
      form = this.renderLoginForm();
      link = html`
        <a
          class="text-sm text-gray-400 hover:text-gray-500"
          href="/log-in/forgot-password"
          @click=${this.navLink}
          >${msg("Forgot your password?")}</a
        >
      `;
    }

    if (this.formState.context.successMessage) {
      successMessage = html`
        <div>
          <btrix-alert variant="success"
            >${this.formState.context.successMessage}</btrix-alert
          >
        </div>
      `;
    }

    return html`
      <article class="w-full max-w-sm grid gap-5">
        ${successMessage}

        <main class="md:bg-white md:shadow-xl md:rounded-lg p-12">
          <div>${form}</div>
        </main>
        <footer class="text-center">${link}</footer>
      </article>
    `;
  }

  private syncFormStateView() {
    const route = this.viewState.route;

    if (route === "login") {
      this.formStateService.send("SHOW_SIGN_IN_WITH_PASSWORD");
    } else if (route === "forgotPassword") {
      this.formStateService.send("SHOW_FORGOT_PASSWORD");
    }
  }

  private renderLoginForm() {
    let formError;

    if (this.formState.context.serverError) {
      formError = html`
        <div class="mb-5">
          <btrix-alert id="formError" variant="danger"
            >${this.formState.context.serverError}</btrix-alert
          >
        </div>
      `;
    }

    return html`
      <form @submit=${this.onSubmitLogIn} aria-describedby="formError">
        <div class="mb-5">
          <btrix-input
            id="email"
            name="username"
            label=${msg("Email")}
            type="email"
            autocomplete="username"
            required
          >
          </btrix-input>
        </div>
        <div class="mb-5">
          <btrix-input
            id="password"
            name="password"
            label=${msg("Password")}
            type="password"
            autocomplete="current-password"
            passwordToggle
            required
          >
          </btrix-input>
        </div>

        ${formError}

        <sl-button
          class="w-full"
          variant="primary"
          ?loading=${this.formState.value === "signingIn"}
          type="submit"
          >${msg("Log in")}</sl-button
        >
      </form>
    `;
  }

  private renderForgotPasswordForm() {
    let formError;

    if (this.formState.context.serverError) {
      formError = html`
        <div class="mb-5">
          <btrix-alert id="formError" variant="danger"
            >${this.formState.context.serverError}</btrix-alert
          >
        </div>
      `;
    }

    return html`
      <form @submit=${this.onSubmitResetPassword} aria-describedby="formError">
        <div class="mb-5">
          <btrix-input
            id="email"
            name="email"
            type="email"
            label="${msg("Your email address")}"
            autocomplete="username"
            required
          >
          </btrix-input>
        </div>

        ${formError}

        <sl-button
          class="w-full"
          variant="primary"
          ?loading=${this.formState.value === "submittingForgotPassword"}
          type="submit"
          >${msg("Request password reset")}</sl-button
        >
      </form>
    `;
  }

  async onSubmitLogIn(event: SubmitEvent) {
    event.preventDefault();
    this.formStateService.send("SUBMIT");

    const formData = new FormData(event.target as HTMLFormElement);
    const username = formData.get("username") as string;
    const password = formData.get("password") as string;

    try {
      const data = await AuthService.login({ email: username, password });

      (data as LoggedInEventDetail).redirectUrl = this.redirectUrl;

      this.dispatchEvent(AuthService.createLoggedInEvent(data));

      // no state update here, since "logged-in" event
      // will result in a route change
    } catch (e: any) {
      if (e.isApiError) {
        // TODO check error details
        this.formStateService.send({
          type: "ERROR",
          detail: {
            serverError: msg("Sorry, invalid username or password"),
          },
        });
      } else {
        this.formStateService.send({
          type: "ERROR",
          detail: {
            serverError: msg("Something went wrong, couldn't sign you in"),
          },
        });
      }
    }
  }

  async onSubmitResetPassword(event: SubmitEvent) {
    event.preventDefault();
    this.formStateService.send("SUBMIT");

    const formData = new FormData(event.target as HTMLFormElement);
    const email = formData.get("email") as string;

    const resp = await fetch("/api/auth/forgot-password", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ email }),
    });

    if (resp.status === 202) {
      this.formStateService.send({
        type: "SUCCESS",
        detail: {
          successMessage: msg(
            "Successfully received your request. You will receive an email to reset your password if your email is found in our system."
          ),
        },
      });
    } else if (resp.status === 422) {
      this.formStateService.send({
        type: "ERROR",
        detail: {
          serverError: msg("That email is not a valid email address"),
        },
      });
    } else {
      this.formStateService.send({
        type: "ERROR",
        detail: {
          serverError: msg("Something unexpected went wrong"),
        },
      });
    }
  }
}
