import {
  Box,
  Button,
  Group,
  Modal,
  ScrollArea,
  Stack,
  Text,
  Title,
} from "@mantine/core";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import {
  ArrowLeft,
  ArrowRight,
  Building2,
  CheckCircle2,
  Clock,
  FileText,
  Globe2,
  PartyPopper,
  ShieldCheck,
  UploadCloud,
  User,
  UserCheck,
} from "lucide-react";
import type { ReactNode } from "react";
import { useCallback, useEffect, useRef, useState } from "react";

import type { RoleLicenseProfile } from "@/components/onboarding/RoleLicenseStep";
import useAuth from "@/hooks/useAuth";
import CompanyProfileForm from "@/pages/accounts/CompanyProfileForm";
import NationalitySelect from "@/pages/settings/NationalitySelect";
import OnboardingRoleSelect from "@/pages/settings/OnboardingRoleSelect";
import { api } from "@/services/api";
import type {
  CompanyNationality,
  CreateCompanyPayload,
  ProfileTypeValue,
} from "@/services/companies.service";
import { companiesService } from "@/services/companies.service";
import type { UpdateProfilePayload } from "@/types/profile";
import { extractApiError } from "@/utils/result";

/** Form steps rendered by CompanyProfileForm. */
type FormStep =
  | "company"
  | "personnel"
  | "contact"
  | "verify"
  | "poa"
  | "documents"
  | "additional";
const FORM_STEPS: FormStep[] = [
  "company",
  "personnel",
  "contact",
  "verify",
  "poa",
  "documents",
  "additional",
];

/** The full onboarding journey: the two pre-form phases + the form steps. */
type WizardStep = "nationality" | "role" | FormStep;
const WIZARD_STEPS: WizardStep[] = ["nationality", "role", ...FORM_STEPS];

/** Icon + title + description shown in the global dialog header per step. */
const STEP_META: Record<
  WizardStep,
  { icon: ReactNode; title: string; description: string }
> = {
  nationality: {
    icon: <Globe2 size={20} />,
    title: "Where is your company registered?",
    description: "This determines the documents we'll ask you to provide.",
  },
  role: {
    icon: <Building2 size={20} />,
    title: "What does your company do?",
    description:
      "Pick any combination of Importer, Exporter and Freight Forwarder — each is set up with its own business license.",
  },
  company: {
    icon: <Building2 size={20} />,
    title: "Company Information",
    description: "Tell us about your company and its registration details.",
  },
  personnel: {
    icon: <User size={20} />,
    title: "General Manager",
    description: "Who is the general manager of the company?",
  },
  contact: {
    icon: <UserCheck size={20} />,
    title: "Contact Person",
    description: "Who should we reach out to about this account?",
  },
  verify: {
    icon: <ShieldCheck size={20} />,
    title: "Verify Contact Person",
    description: "Confirm the contact phone with a one-time SMS code.",
  },
  poa: {
    icon: <FileText size={20} />,
    title: "Power of Attorney",
    description: "Optionally add a representative with power of attorney.",
  },
  documents: {
    icon: <UploadCloud size={20} />,
    title: "Upload Documents",
    description: "Provide the required company documents.",
  },
  additional: {
    icon: <CheckCircle2 size={20} />,
    title: "Business License",
    description: "Upload a business license for each operational profile.",
  },
};

interface OnboardingWizardDialogProps {
  opened: boolean;
  /** Dismiss the dialog (user clicked the close icon). */
  onClose: () => void;
}

/**
 * The company type for the onboarding selection. Importer / Exporter / Freight
 * Forwarder are all services a single "customer" company can hold (in any
 * combination), each with its own business license — so the company is always
 * registered as a "customer".
 */
function companyTypeForRoles(_roles: string[]): string {
  return "customer";
}

/** Document upload setting code per company nationality. */
function documentSettingCode(nationality: CompanyNationality): string {
  return nationality === "foreign"
    ? "company_onboarding_documents_foreign"
    : "company_onboarding_documents_ethiopian";
}

/**
 * First-run onboarding wizard with a "draft-first" flow: picking the role(s)
 * immediately creates a draft company + profile on the backend, so every
 * subsequent step saves its data incrementally (PATCH /profile, /onboarding-step)
 * against existing rows. The final step uploads documents and marks onboarding
 * complete. Dismissable — the gate keeps it reachable until finished.
 */
export default function OnboardingWizardDialog({
  opened,
  onClose,
}: OnboardingWizardDialogProps) {
  const queryClient = useQueryClient();
  const { user, company, onboardingStep, onboardingCompleted } = useAuth();

  const existingProfiles = company?.company?.companyProfiles ?? [];
  const companyAlreadyStarted = Boolean(company?.company?.id);
  // A draft can exist with zero operational profiles (e.g. an interrupted start).
  // Such a draft must re-run role selection so the profiles actually get created
  // — otherwise the user is stuck with nothing to upload a license against.
  const hasOperationalProfiles = existingProfiles.length > 0;
  const savedNationality =
    (company?.company?.nationality as CompanyNationality | null) ?? null;

  // Resume position from the backend-persisted step.
  const resumeFormStep: FormStep = FORM_STEPS.includes(
    onboardingStep as FormStep,
  )
    ? (onboardingStep as FormStep)
    : "company";

  // Phases: nationality → role → form. If a draft already exists, resume
  // straight into the form with nationality + roles pre-selected.
  const [phase, setPhase] = useState<"nationality" | "role" | "form">(
    companyAlreadyStarted
      ? hasOperationalProfiles
        ? "form"
        : "role"
      : "nationality",
  );
  const [nationality, setNationality] = useState<CompanyNationality | null>(
    savedNationality,
  );
  const [roles, setRoles] = useState<string[]>(
    existingProfiles.map((p) => p.type),
  );
  const [documentFiles, setDocumentFiles] = useState<
    Record<string, File | File[] | null>
  >({});
  // Newly-selected business-license files per company_profile id.
  const [licenseFiles, setLicenseFiles] = useState<Record<string, File[]>>({});
  const [startError, setStartError] = useState<string | null>(null);
  // Mirror of CompanyProfileForm's active step so the global header + progress
  // pill can reflect it (the form no longer renders its own stepper).
  const [formStep, setFormStep] = useState<FormStep>(resumeFormStep);
  // Once submission succeeds we swap the whole wizard body for a congratulations
  // panel, and keep the modal open (the gate would otherwise tear it down the
  // moment onboardingCompleted flips true).
  const [completed, setCompleted] = useState(false);

  // Saved profile data, for rehydrating the form fields after a refresh.
  const profileQuery = useQuery(
    api.companies.getProfile.queryOptions({
      enabled: companyAlreadyStarted,
      retry: false,
      refetchOnWindowFocus: false,
    }),
  );

  // Server-driven onboarding requirements: the backend decides which document
  // set applies (by nationality) and what's still outstanding, so the client
  // never makes that choice itself. This is the heavier "second request" — it's
  // only issued while onboarding is still incomplete; once the getInfo flag says
  // we're done, it never fires.
  const requirementsQuery = useQuery(
    api.companies.onboardingRequirements.queryOptions({
      enabled: companyAlreadyStarted && !onboardingCompleted,
      retry: false,
      refetchOnWindowFocus: false,
    }),
  );

  const refreshInfo = useCallback(
    () =>
      queryClient.invalidateQueries({
        queryKey: api.companies.getInfo.queryKey(),
      }),
    [queryClient],
  );

  // Begin onboarding: create the draft company + profile + role(s) + nationality.
  const startMutation = useMutation({
    mutationFn: (vars: {
      companyType: string;
      roles: ProfileTypeValue[];
      nationality?: CompanyNationality;
    }) => api.companies.startOnboarding.call(vars),
    onSuccess: async () => {
      await refreshInfo();
      setPhase("form");
    },
    onError: (err) => setStartError(extractApiError(err).message),
  });

  // Finalize: upload per-role license files + company documents, then complete.
  const finishMutation = useMutation({
    mutationFn: async () => {
      const companyId = company?.company?.id;
      // Per-role business licenses (file model, resource=company_profiles).
      for (const [profileId, files] of Object.entries(licenseFiles)) {
        if (files.length > 0) {
          await companiesService.uploadProfileLicense(profileId, files);
        }
      }
      // Nationality-based company documents (resource=companies).
      const hasDocs = Object.values(documentFiles).some(
        (f) => f !== null && (Array.isArray(f) ? f.length > 0 : true),
      );
      if (companyId && hasDocs) {
        await companiesService.uploadDocuments(companyId, documentFiles);
      }
      return api.companies.completeOnboarding.call();
    },
    onSuccess: async () => {
      await refreshInfo();
      setCompleted(true);
    },
    onError: (err) => setStartError(extractApiError(err).message),
  });

  // Persist the resume step to the backend, but only ever move FORWARD — going
  // Back must never downgrade the furthest step the user reached, so reopening
  // always lands on the furthest step.
  const furthestIdxRef = useRef(FORM_STEPS.indexOf(resumeFormStep));
  const persistStep = useCallback((step: string) => {
    const idx = FORM_STEPS.indexOf(step as FormStep);
    if (idx < 0 || idx <= furthestIdxRef.current) return;
    furthestIdxRef.current = idx;
    api.companies.setOnboardingStep.call({ step }).catch(() => { });
  }, []);

  // Mirror the form's step locally (for the header/pill) and persist it.
  const handleStepChange = useCallback(
    (step: string) => {
      setFormStep(step as FormStep);
      persistStep(step);
    },
    [persistStep],
  );

  // The company query may resolve AFTER this dialog mounts (it's kept mounted by
  // the gate), so the phase/roles/nationality initial state can be stale — a
  // draft that already exists would otherwise leave us stuck on the first
  // (nationality) phase. Once a draft loads, jump straight into the form with
  // the persisted roles/nationality. Runs once per resumed draft.
  const resumedRef = useRef(false);
  useEffect(() => {
    if (!companyAlreadyStarted || resumedRef.current) return;
    resumedRef.current = true;
    setRoles(existingProfiles.map((p) => p.type));
    setNationality(savedNationality);
    // Resume into the form only when profiles exist; otherwise send the user to
    // role selection so the missing operational profiles get created.
    setPhase(hasOperationalProfiles ? "form" : "role");
    const idx = FORM_STEPS.indexOf(resumeFormStep);
    if (idx > furthestIdxRef.current) furthestIdxRef.current = idx;
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [companyAlreadyStarted, resumeFormStep]);

  const handleNationalityContinue = useCallback(() => {
    if (nationality) setPhase("role");
  }, [nationality]);

  const handleRolesContinue = useCallback(() => {
    setStartError(null);
    startMutation.mutate({
      companyType: companyTypeForRoles(roles),
      roles: roles as ProfileTypeValue[],
      nationality: nationality ?? undefined,
    });
  }, [roles, nationality, startMutation]);

  // Note: no "back to role selection" — once the draft is created the role(s)
  // are fixed; the form's first-step Back is a no-op so progress never resets.
  const handleBackToRoles = useCallback(() => { }, []);

  // Save the current step's fields to the draft (PATCH /profile). Returns the
  // server error message on failure so the form can show it (e.g. duplicate TIN).
  const saveStep = useCallback(
    async (
      data: Partial<UpdateProfilePayload>,
    ): Promise<{ ok: true } | { ok: false; error: string }> => {
      try {
        await api.companies.updateProfile.call(data as UpdateProfilePayload);
        return { ok: true };
      } catch (err) {
        return { ok: false, error: extractApiError(err).message };
      }
    },
    [],
  );

  // Auto-upload the documents the user just selected as they leave the documents
  // step. Only the in-memory selections are sent; once uploaded they're cleared
  // (so the final submit never re-uploads them) and the requirements query is
  // refreshed so the "Already uploaded" badges light up. Partial uploads are
  // allowed — the user may continue even with required docs still outstanding.
  const handleUploadDocuments = useCallback(async (): Promise<
    { ok: true } | { ok: false; error: string }
  > => {
    const companyId = company?.company?.id;
    const hasNew = Object.values(documentFiles).some(
      (f) => f !== null && (Array.isArray(f) ? f.length > 0 : true),
    );
    if (!companyId || !hasNew) return { ok: true };
    try {
      await companiesService.uploadDocuments(companyId, documentFiles);
      setDocumentFiles({});
      await queryClient.invalidateQueries({
        queryKey: api.companies.onboardingRequirements.queryKey(),
      });
      return { ok: true };
    } catch (err) {
      return { ok: false, error: extractApiError(err).message };
    }
  }, [company?.company?.id, documentFiles, queryClient]);

  // Final confirm step → finalize onboarding (no company create; it already
  // exists as a draft that's been filled in step-by-step).
  const handleSubmit = useCallback(
    (_payload: CreateCompanyPayload) => {
      finishMutation.mutate();
    },
    [finishMutation],
  );

  if (!user) return null;

  // Any non-empty combination of importer/exporter/freight-forwarder is valid.
  const rolesValid = roles.length > 0;
  // Documents depend on nationality; fall back to the saved one (resume) then ethiopian.
  const effectiveNationality: CompanyNationality =
    nationality ?? savedNationality ?? "ethiopian";

  // Per-role license cards for the final step (from the created profiles).
  const roleProfiles: RoleLicenseProfile[] = existingProfiles.map((p) => ({
    id: p.id,
    type: p.type,
    reference: p.reference,
    existingFiles: p.licenseFiles ?? [],
  }));

  // The active step across the whole journey, driving the header + progress pill.
  const activeStep: WizardStep = phase === "form" ? formStep : phase;
  const stepMeta = STEP_META[activeStep];
  const activeIdx = WIZARD_STEPS.indexOf(activeStep);

  // Closing from the congratulations panel also clears the completed flag so a
  // future reopen (shouldn't happen once onboarded) starts clean.
  const handleClose = useCallback(() => {
    if (completed) setCompleted(false);
    onClose();
  }, [completed, onClose]);

  // Prefer the backend-resolved document code; fall back to the local mapping
  // only until the requirements query lands (the documents step is reached well
  // after the draft — and thus the requirements — exist).
  const resolvedDocumentSettingCode =
    requirementsQuery.data?.documentSettingCode ??
    documentSettingCode(effectiveNationality);

  // Server-confirmed document state, used both to badge already-uploaded fields
  // and to keep a refreshed resume from over-shooting the documents step.
  const requirementDocuments = requirementsQuery.data?.documents ?? [];
  const uploadedDocumentKeys = requirementDocuments
    .filter((d) => d.uploaded)
    .map((d) => d.fileKey);
  // If any REQUIRED document is still missing, the resume must not rest past the
  // documents step (don't skip to Business License) — clamp it back. This only
  // changes the target once requirements load; the form follows the correction
  // as long as the user hasn't navigated yet.
  const requiredDocsMissing = requirementDocuments.some(
    (d) => d.isRequired && !d.uploaded,
  );
  const effectiveResumeStep: FormStep =
    requiredDocsMissing &&
    FORM_STEPS.indexOf(resumeFormStep) > FORM_STEPS.indexOf("documents")
      ? "documents"
      : resumeFormStep;

  const formProps = {
    documentSettingCode: resolvedDocumentSettingCode,
    documentFiles,
    onDocumentFilesChange: setDocumentFiles,
    user,
    onSubmit: handleSubmit,
    isPending: finishMutation.isPending,
    onBack: handleBackToRoles,
    hideFirstStepBack: true,
    initialStep: effectiveResumeStep,
    resyncOpen: opened,
    onStepChange: handleStepChange,
    onSaveStep: saveStep,
    rehydrate: profileQuery.data ?? null,
    roleProfiles,
    licenseFiles,
    onLicenseChange: setLicenseFiles,
    uploadedDocumentKeys,
    onUploadDocuments: handleUploadDocuments,
    // Surface a failed final submit (license/document upload or complete) inside
    // the form — otherwise the server message (e.g. a 500) would be invisible on
    // the submit step.
    submitError: phase === "form" ? startError : null,
  };

  return (
    <Modal
      opened={opened || completed}
      onClose={handleClose}
      withCloseButton={!completed}
      closeOnClickOutside={false}
      closeOnEscape={!completed}
      size={1440}
      radius="lg"
      padding="xl"
      centered
      keepMounted
      scrollAreaComponent={ScrollArea.Autosize}
      overlayProps={{ backgroundOpacity: 0.55, blur: 4 }}
      styles={{
        header: {
          alignItems: "flex-start",
        },
        title: {
          flex: 1,
        },
      }}
      title={
        completed ? null : (
          <Stack gap="md">
            <Box>
              <Group gap="sm" mb={4}>
                {stepMeta.icon}
                <Title order={3}>{stepMeta.title}</Title>
              </Group>
              <Text c="edr-muted" size="sm">
                {stepMeta.description}
              </Text>
            </Box>
            <ProgressPill current={activeIdx} total={WIZARD_STEPS.length} />
          </Stack>
        )
      }
    >
      {completed ? (
        <OnboardingCompletePanel onClose={handleClose} />
      ) : (
        <Stack gap="xl">
          {phase === "nationality" ? (
            <Stack gap="lg">
              <NationalitySelect
                value={nationality}
                onChange={setNationality}
                embedded
              />
              <Group justify="flex-end" pt="xs">
                <Button
                  color="edr-green"
                  onClick={handleNationalityContinue}
                  disabled={!nationality}
                  rightSection={<ArrowRight size={16} />}
                >
                  Continue
                </Button>
              </Group>
            </Stack>
          ) : phase === "role" ? (
            <Stack gap="lg">
              <OnboardingRoleSelect
                value={roles}
                onChange={setRoles}
                embedded
              />
              {startError && (
                <Text size="sm" c="red">
                  {startError}
                </Text>
              )}
              <Group justify="space-between" pt="xs">
                <Button
                  variant="default"
                  leftSection={<ArrowLeft size={16} />}
                  onClick={() => setPhase("nationality")}
                >
                  Back
                </Button>
                <Button
                  color="edr-green"
                  onClick={handleRolesContinue}
                  disabled={!rolesValid}
                  loading={startMutation.isPending}
                  rightSection={
                    startMutation.isPending ? undefined : (
                      <ArrowRight size={16} />
                    )
                  }
                >
                  Continue
                </Button>
              </Group>
            </Stack>
          ) : (
            <CompanyProfileForm {...formProps} />
          )}
        </Stack>
      )}
    </Modal>
  );
}

/**
 * Replaces the wizard body once onboarding is submitted: congratulates the user
 * and sets the expectation that their company is now under review, and that
 * bookings unlock per profile as the team approves each one.
 */
function OnboardingCompletePanel({ onClose }: { onClose: () => void }) {
  return (
    <Stack gap="lg" align="center" py="md" ta="center">
      <Box
        className="flex h-16 w-16 items-center justify-center rounded-full"
        style={{ background: "var(--mantine-color-edr-green-1)" }}
      >
        <PartyPopper
          size={32}
          className="text-[var(--mantine-color-edr-green-7)]"
        />
      </Box>

      <Box>
        <Title order={3}>You're all set!</Title>
        <Text c="edr-muted" size="sm" mt={4} maw={460}>
          Thanks for completing your company profile. Your application has been
          submitted and is now with our team for review.
        </Text>
      </Box>

      <Stack
        gap="sm"
        w="100%"
        maw={460}
        p="md"
        className="rounded-lg"
        style={{ background: "var(--mantine-color-edr-green-0)" }}
      >
        <Group gap="sm" wrap="nowrap" align="flex-start">
          <Clock
            size={18}
            className="mt-0.5 shrink-0 text-[var(--mantine-color-edr-green-7)]"
          />
          <Text size="sm" ta="left">
            Each operational profile (importer, exporter, freight forwarder) is
            reviewed and approved individually.
          </Text>
        </Group>
        <Group gap="sm" wrap="nowrap" align="flex-start">
          <ShieldCheck
            size={18}
            className="mt-0.5 shrink-0 text-[var(--mantine-color-edr-green-7)]"
          />
          <Text size="sm" ta="left">
            You can start creating bookings under a profile as soon as it's
            approved — we'll let you know the moment that happens.
          </Text>
        </Group>
      </Stack>

      <Button color="edr-green" size="md" onClick={onClose} mt="xs">
        Go to my dashboard
      </Button>
    </Stack>
  );
}

/**
 * Continuous progress pill: a single rounded track that fills left-to-right as
 * the user advances, with faint ticks marking each step boundary.
 */
function ProgressPill({ current, total }: { current: number; total: number }) {
  const pct = total > 0 ? ((current + 1) / total) * 100 : 0;
  return (
    <Box className="relative h-1.5 w-full overflow-hidden rounded-full bg-edr-border">
      <Box
        className="absolute inset-y-0 left-0 rounded-full bg-[var(--mantine-color-edr-green-6)] transition-[width] duration-500 ease-out"
        style={{ width: `${pct}%` }}
      />
    </Box>
  );
}
