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

import type { CurrentUser } from "../types/user";
import LiteElement, { html } from "../utils/LiteElement";
import { needLogin } from "../utils/auth";
import type { AuthState, Auth } from "../utils/AuthService";
import AuthService from "../utils/AuthService";

@localized()
class RequestVerify extends LitElement {
  @property({ type: String })
  email!: string;

  @state()
  private isRequesting: boolean = false;

  @state()
  private requestSuccess: boolean = false;

  createRenderRoot() {
    return this;
  }

  render() {
    if (this.requestSuccess) {
      return html`
        <div class="text-sm text-gray-400 inline-flex items-center">
          <sl-icon class="mr-1" name="check-lg"></sl-icon> ${msg("Sent", {
            desc: "Status message after sending verification email",
          })}
        </div>
      `;
    }

    return html`
      <span
        class="text-sm text-blue-400 hover:text-blue-500"
        role="button"
        ?disabled=${this.isRequesting}
        @click=${this.requestVerification}
      >
        ${this.isRequesting
          ? msg("Sending...")
          : msg("Resend verification email")}
      </span>
    `;
  }

  private async requestVerification() {
    this.isRequesting = true;

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

    switch (resp.status) {
      case 202:
        this.requestSuccess = true;
        break;
      default:
        // TODO generic toast error
        break;
    }

    this.isRequesting = false;
  }
}
customElements.define("btrix-request-verify", RequestVerify);

type FormContext = {
  successMessage?: string;
  serverError?: string;
  fieldErrors: { [fieldName: string]: string };
};
type FormSuccessEvent = {
  type: "SUCCESS";
  detail: {
    successMessage?: FormContext["successMessage"];
  };
};
type FormErrorEvent = {
  type: "ERROR";
  detail: {
    serverError?: FormContext["serverError"];
    fieldErrors?: FormContext["fieldErrors"];
  };
};
type FormEvent =
  | { type: "EDIT" }
  | { type: "CANCEL" }
  | { type: "SUBMIT" }
  | FormSuccessEvent
  | FormErrorEvent;

type FormTypestate =
  | {
      value: "readOnly";
      context: FormContext;
    }
  | {
      value: "editingForm";
      context: FormContext;
    }
  | {
      value: "submittingForm";
      context: FormContext;
    };

const initialContext = {
  fieldErrors: {},
};

const machine = createMachine<FormContext, FormEvent, FormTypestate>(
  {
    id: "changePasswordForm",
    initial: "readOnly",
    context: initialContext,
    states: {
      ["readOnly"]: {
        on: {
          EDIT: {
            target: "editingForm",
            actions: "reset",
          },
        },
      },
      ["editingForm"]: {
        on: { CANCEL: "readOnly", SUBMIT: "submittingForm" },
      },
      ["submittingForm"]: {
        on: {
          SUCCESS: {
            target: "readOnly",
            actions: "setSuccessMessage",
          },
          ERROR: {
            target: "editingForm",
            actions: "setError",
          },
        },
      },
    },
  },
  {
    actions: {
      reset: assign(() => initialContext),
      setSuccessMessage: assign((context, event) => ({
        ...context,
        ...(event as FormSuccessEvent).detail,
      })),
      setError: assign((context, event) => ({
        ...context,
        ...(event as FormErrorEvent).detail,
      })),
    },
  }
);

@needLogin
@localized()
export class AccountSettings extends LiteElement {
  private formStateService = interpret(machine);

  @property({ type: Object })
  authState?: AuthState;

  @property({ type: Object })
  userInfo?: CurrentUser;

  @state()
  private formState = machine.initialState;

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

    this.formStateService.start();
  }

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

  render() {
    const showForm =
      this.formState.value === "editingForm" ||
      this.formState.value === "submittingForm";
    let successMessage;
    let verificationMessage;

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

    if (this.userInfo) {
      if (this.userInfo.isVerified) {
        verificationMessage = html`
          <sl-tag variant="success" size="small"
            >${msg("verified", {
              desc: "Status text when user email is verified",
            })}</sl-tag
          >
        `;
      } else {
        verificationMessage = html`
          <sl-tag class="mr-2" variant="warning" size="small"
            >${msg("unverified", {
              desc: "Status text when user email is not yet verified",
            })}</sl-tag
          >

          <btrix-request-verify
            email=${this.userInfo.email}
          ></btrix-request-verify>
        `;
      }
    }

    return html`<div class="grid gap-4">
      <h1 class="text-xl font-semibold">${msg("Account Settings")}</h1>

      ${successMessage}

      <section class="p-4 md:p-8 border rounded-lg grid gap-6">
        <div>
          <div class="mb-1 text-gray-500">${msg("Name")}</div>
          <div class="inline-flex items-center">
            <span class="mr-3">${this.userInfo?.name}</span>
          </div>
        </div>

        <div>
          <div class="mb-1 text-gray-500">${msg("Email")}</div>
          <div class="inline-flex items-center">
            <span class="mr-3">${this.userInfo?.email}</span>
            ${verificationMessage}
          </div>
        </div>

        ${showForm
          ? this.renderChangePasswordForm()
          : html`
              <div>
                <sl-button
                  variant="primary"
                  outline
                  @click=${() => this.formStateService.send("EDIT")}
                  >${msg("Change password")}</sl-button
                >
              </div>
            `}
      </section>
    </div>`;
  }

  renderChangePasswordForm() {
    const passwordFieldError = this.formState.context.fieldErrors.password;
    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` <div class="max-w-sm">
      <h3 class="font-semibold mb-3">${msg("Change password")}</h3>
      <form @submit=${this.onSubmit} aria-describedby="formError">
        <div class="mb-5">
          <sl-input
            id="password"
            class="${passwordFieldError ? "text-danger" : ""}"
            name="password"
            type="password"
            label="${msg("Current password")}"
            aria-describedby="passwordError"
            autocomplete="current-password"
            password-toggle
            required
          >
          </sl-input>
          ${passwordFieldError
            ? html`<div id="passwordError" class="text-danger" role="alert">
                ${passwordFieldError}
              </div>`
            : ""}
        </div>
        <div class="mb-5">
          <sl-input
            id="newPassword"
            name="newPassword"
            type="password"
            label="${msg("New password")}"
            autocomplete="new-password"
            password-toggle
            required
          >
          </sl-input>
        </div>

        ${formError}

        <div>
          <sl-button
            variant="primary"
            ?loading=${this.formState.value === "submittingForm"}
            type="submit"
            >${msg("Update password")}</sl-button
          >
          <sl-button
            variant="text"
            @click=${() => this.formStateService.send("CANCEL")}
            >${msg("Cancel")}</sl-button
          >
        </div>
      </form>
    </div>`;
  }

  async onSubmit(event: any) {
    event.preventDefault();
    if (!this.authState) return;

    this.formStateService.send("SUBMIT");

    const formData = new FormData(event.target);
    let nextAuthState: Auth | null = null;

    try {
      nextAuthState = await AuthService.login({
        email: this.authState.username,
        password: formData.get("password") as string,
      });

      this.dispatchEvent(AuthService.createLoggedInEvent(nextAuthState));
    } catch (e: any) {
      console.debug(e);
    }

    if (!nextAuthState) {
      this.formStateService.send({
        type: "ERROR",
        detail: {
          fieldErrors: {
            password: msg("Wrong password"),
          },
        },
      });
      return;
    }

    const params = {
      password: formData.get("newPassword"),
    };

    try {
      await this.apiFetch("/users/me", nextAuthState, {
        method: "PATCH",
        body: JSON.stringify(params),
      });

      this.formStateService.send({
        type: "SUCCESS",
        detail: {
          successMessage: "Successfully updated password",
        },
      });
    } catch (e) {
      console.error(e);

      this.formStateService.send({
        type: "ERROR",
        detail: {
          serverError: msg("Something went wrong changing password"),
        },
      });
    }
  }
}
