import { useMemo, useState } from "react";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import {
  CheckCircle2,
  Save,
  ShieldCheck,
  UserCog,
  XCircle,
} from "lucide-react";
import {
  Alert,
  Button,
  Card,
  Group,
  Modal,
  PinInput,
  Stack,
  Text,
  TextInput,
  Title,
} from "@mantine/core";
import { api } from "@/services/api";
import {
  ControlledPhoneField,
  isValidPhone,
  toEthiopianE164,
} from "@/components/PhoneField";
import type { AuthUser, ContactChannel } from "@/types/auth";
import ChangePasswordCard from "./ChangePasswordCard";

const schema = z.object({
  phoneNumber: z
    .string()
    .min(1, "Phone number is required")
    .refine(isValidPhone, "Enter a valid phone number"),
  email: z.string().min(1, "Email is required").email("Enter a valid email"),
  nameEn: z.string().min(1, "Name is required"),
  nameAm: z.string().min(1, "Amharic name is required"),
});

type FormData = z.infer<typeof schema>;

/** A contact change that still needs its code entered. */
interface PendingChange {
  channel: ContactChannel;
  value: string;
}

const CHANNEL_LABEL: Record<ContactChannel, string> = {
  phone: "phone number",
  email: "email address",
};

const normaliseEmail = (v: string) => v.trim().toLowerCase();

interface TabAccountProps {
  user: AuthUser;
}

/**
 * The signed-in user's own account — the phone and email that OTPs and SMS
 * notifications are actually delivered to. Distinct from the company profile
 * tabs, which hold business contact details for the organisation.
 *
 * Changing phone or email is verified: the server sends a code to the NEW value
 * and only writes it once the code comes back, so a typo'd number can never
 * silently take over the account's notifications. Each code is bound to a single
 * channel, so changing both walks the user through one verification per channel.
 */
export default function TabAccount({ user }: TabAccountProps) {
  const queryClient = useQueryClient();
  // Head of the queue is the change currently being verified. Changing phone AND
  // email in one save enqueues both — a code proves one channel, never two.
  const [queue, setQueue] = useState<PendingChange[]>([]);
  const [sentTo, setSentTo] = useState<string | null>(null);
  const [completed, setCompleted] = useState<ContactChannel[]>([]);
  const [otp, setOtp] = useState("");

  const current = queue[0] ?? null;
  const step = completed.length + 1;
  const totalSteps = completed.length + queue.length;

  const defaultValues = useMemo(
    (): FormData => ({
      // Normalise to the same E.164 shape the phone input emits. Some accounts
      // store a local `0911…`; comparing raw against the field's `+2519…` would
      // read as "changed" on every save and hijack the email's turn.
      phoneNumber: toEthiopianE164(user.phoneNumber),
      email: user.email ?? "",
      nameEn: user.name?.en ?? "",
      nameAm: user.name?.am ?? "",
    }),
    [user],
  );

  const {
    register,
    control,
    handleSubmit,
    reset,
    formState: { errors, isDirty },
  } = useForm<FormData>({
    resolver: zodResolver(schema),
    values: defaultValues,
    // Verifying one channel refetches the user, which re-syncs `values`. Without
    // keepDirtyValues that resync would silently discard an edit the user has
    // typed into the *other* field but not yet verified.
    resetOptions: { keepDirtyValues: true },
  });

  const refreshUser = () =>
    queryClient.invalidateQueries({ queryKey: api.auth.getMyInfo.queryKey() });

  /** Name needs no proof of possession, so it saves straight through. */
  const nameMutation = useMutation({
    mutationFn: (data: FormData) =>
      api.account.updateName.call({
        name: { en: data.nameEn, am: data.nameAm },
      }),
    onSuccess: refreshUser,
  });

  /** Step 1 of a contact change: ask the server to code the new value. */
  const otpMutation = useMutation({
    mutationFn: (change: PendingChange) =>
      api.account.sendContactOtp.call(change),
    onSuccess: (res) => {
      setOtp("");
      setSentTo(res.sentTo);
    },
    onError: () => {
      // Could not even send — drop the flow rather than strand the user in a
      // modal asking for a code that was never issued.
      setQueue([]);
      setSentTo(null);
    },
  });

  /** Step 2: hand the code back; the server verifies and writes atomically. */
  const contactMutation = useMutation({
    mutationFn: (body: PendingChange & { otp: string }) =>
      api.account.updateContact.call(body),
    onSuccess: async (_res, body) => {
      setOtp("");
      setSentTo(null);
      setCompleted((prev) => [...prev, body.channel]);
      await refreshUser();

      // Advance to the next queued channel, keeping the modal open so a
      // both-changed save is one continuous flow.
      const rest = queue.slice(1);
      setQueue(rest);
      if (rest[0]) otpMutation.mutate(rest[0]);
    },
  });

  const startQueue = (changes: PendingChange[]) => {
    setCompleted([]);
    setQueue(changes);
    otpMutation.mutate(changes[0]);
  };

  const cancelQueue = () => {
    setQueue([]);
    setSentTo(null);
    setOtp("");
    contactMutation.reset();
  };

  const onSubmit = (data: FormData) => {
    setCompleted([]);

    const changes: PendingChange[] = [];
    if (toEthiopianE164(data.phoneNumber) !== defaultValues.phoneNumber) {
      changes.push({ channel: "phone", value: data.phoneNumber });
    }
    if (normaliseEmail(data.email) !== normaliseEmail(defaultValues.email)) {
      changes.push({ channel: "email", value: normaliseEmail(data.email) });
    }

    // Name carries no verification, so it saves alongside rather than queueing.
    if (
      data.nameEn !== defaultValues.nameEn ||
      data.nameAm !== defaultValues.nameAm
    ) {
      nameMutation.mutate(data);
    }

    if (changes.length) startQueue(changes);
  };

  const errorMessage = (err: unknown): string => {
    const res = (
      err as { response?: { data?: { message?: string | string[] } } }
    )?.response?.data?.message;
    if (Array.isArray(res)) return res[0];
    return res ?? "Something went wrong. Please try again.";
  };

  const busy =
    otpMutation.isPending ||
    contactMutation.isPending ||
    nameMutation.isPending;

  const savedSummary =
    completed.length && !queue.length
      ? `Your ${completed.map((c) => CHANNEL_LABEL[c]).join(" and ")} ${
          completed.length > 1 ? "were" : "was"
        } verified and updated`
      : null;

  return (
    <Stack gap="lg">
      <Card padding="lg">
        <Group gap="sm" mb="xs">
          <UserCog size={20} />
          <Title order={3}>Account</Title>
        </Group>
        <Text c="edr-muted" size="sm" mb="lg">
          Your login details. Verification codes and SMS notifications are sent
          to the phone number below.
        </Text>

        <form onSubmit={handleSubmit(onSubmit)}>
          <Stack gap="md">
            <ControlledPhoneField
              control={control}
              name="phoneNumber"
              label="Phone Number"
              required
            />

            <TextInput
              label="Email"
              placeholder="you@company.com"
              error={errors.email?.message}
              {...register("email")}
            />

            <TextInput
              label="Full Name"
              placeholder="Abebe Bekele"
              error={errors.nameEn?.message}
              {...register("nameEn")}
            />

            <TextInput
              label="Full Name (Amharic)"
              placeholder="አበበ በቀለ"
              error={errors.nameAm?.message}
              {...register("nameAm")}
            />
          </Stack>

          <Text c="edr-muted" size="xs" mt="sm">
            Changing your phone number or email requires a verification code
            sent to the new one.
          </Text>

          <Group
            justify="space-between"
            mt="xl"
            pt="md"
            style={{ borderTop: "1px solid var(--mantine-color-edr-border-0)" }}
          >
            <Group gap="xs">
              {savedSummary && (
                <Group gap={6} c="green">
                  <CheckCircle2 size={16} />
                  <Text size="sm" fw={500}>
                    {savedSummary}
                  </Text>
                </Group>
              )}
              {nameMutation.isSuccess && !savedSummary && !queue.length && (
                <Group gap={6} c="green">
                  <CheckCircle2 size={16} />
                  <Text size="sm" fw={500}>
                    Saved successfully
                  </Text>
                </Group>
              )}
              {(otpMutation.isError || nameMutation.isError) && (
                <Group gap={6} c="red">
                  <XCircle size={16} />
                  <Text size="sm" fw={500}>
                    {errorMessage(otpMutation.error ?? nameMutation.error)}
                  </Text>
                </Group>
              )}
            </Group>
            <Group gap="md">
              <Button
                type="button"
                variant="outline"
                disabled={busy || !isDirty}
                onClick={() => reset()}
              >
                Reset
              </Button>
              <Button
                type="submit"
                leftSection={<Save size={16} />}
                loading={busy}
              >
                Save Changes
              </Button>
            </Group>
          </Group>
        </form>

        <Modal
          opened={current !== null}
          onClose={cancelQueue}
          title="Verify your new contact details"
          centered
        >
          {current && (
            <Stack gap="md">
              {totalSteps > 1 && (
                <Text size="sm" c="edr-muted">
                  Step {step} of {totalSteps}
                </Text>
              )}

              <Alert icon={<ShieldCheck size={16} />} color="blue">
                {sentTo ? (
                  <>
                    We sent a 6-digit code to <b>{sentTo}</b>. Enter it to
                    confirm your new {CHANNEL_LABEL[current.channel]}.
                  </>
                ) : (
                  <>
                    Sending a code to your new {CHANNEL_LABEL[current.channel]}…
                  </>
                )}
              </Alert>

              {completed.length > 0 && queue.length > 0 && (
                <Group gap={6} c="green">
                  <CheckCircle2 size={16} />
                  <Text size="sm">
                    {CHANNEL_LABEL[completed[completed.length - 1]]} updated —
                    one more to confirm.
                  </Text>
                </Group>
              )}

              <PinInput
                length={6}
                type="number"
                oneTimeCode
                value={otp}
                onChange={setOtp}
                disabled={!sentTo || otpMutation.isPending}
                aria-label="Verification code"
              />

              {contactMutation.isError && (
                <Group gap={6} c="red">
                  <XCircle size={16} />
                  <Text size="sm">{errorMessage(contactMutation.error)}</Text>
                </Group>
              )}

              <Group justify="space-between">
                <Button
                  variant="subtle"
                  disabled={otpMutation.isPending || !sentTo}
                  onClick={() => {
                    // Clear any "invalid code" error first: the user is asking for
                    // a fresh code, not retrying the old one, so leaving the
                    // failure on screen would describe a request they didn't make.
                    contactMutation.reset();
                    otpMutation.mutate(current);
                  }}
                >
                  Resend code
                </Button>
                <Button
                  loading={contactMutation.isPending}
                  disabled={otp.length !== 6 || !sentTo}
                  onClick={() => contactMutation.mutate({ ...current, otp })}
                >
                  Verify &amp; Save
                </Button>
              </Group>
            </Stack>
          )}
        </Modal>
      </Card>

      <ChangePasswordCard />
    </Stack>
  );
}
