import { useEffect, useMemo, useRef, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import {
  Alert,
  Button,
  Center,
  Container,
  Divider,
  Grid,
  Group,
  Loader,
  Modal,
  Paper,
  Stack,
  Stepper,
  Text,
  Title,
} from '@mantine/core';
import { IconAlertTriangle, IconCheck, IconInfoCircle, IconPencil, IconTrash } from '@tabler/icons-react';
import { notifications } from '@mantine/notifications';
import {
  PHYSICAL_BOUNDS,
  SEAFARER_REGISTRATION_FIELD_LABELS,
  SEAFARER_REGISTRATION_STATUS_TONES,
  SEAFARER_REGISTRATION_STATUS_LABELS,
  extractErrorMessage,
  extractValidationIssues,
  isEthiopianNationality,
  useCancelSeafarerRegistrationMutation,
  useGetAttachmentsQuery,
  useFillRegistrationDocumentsFromVaultMutation,
  useGetLicenseTypeRequirementsQuery,
  useGetMySeafarerRegistrationQuery,
  useSaveSeafarerRegistrationMutation,
  useStartSeafarerRegistrationMutation,
  useSubmitSeafarerRegistrationMutation,
  type SaveSeafarerRegistration,
  type SeafarerRegistration,
  type ValidationIssue,
} from '@ema-platform/api';
import { splitPersonName, StatusBadge } from '@ema-platform/ui';
import { useCurrentProfile, type CurrentProfile } from '@ema-platform/auth';
import { useAppSelector } from '../../../store/hooks';
import { CheckboxField, type AnswerKey } from '../components/fields';
import { ApplicantDetailsStep, EmergencyContactStep, IdentityDetailsStep } from '../components/steps';
import { RegistrationDocuments, documentSlots } from '../components/RegistrationDocuments';
import { RegistrationSummary } from '../components/RegistrationSummary';

const STEPS = [
  { label: 'Identity', description: 'Who you are' },
  { label: 'Details', description: 'Address & physical' },
  { label: 'Contact & Medical', description: 'Emergency & fitness' },
  { label: 'Documents', description: 'Upload evidence' },
  { label: 'Review', description: 'Check & submit' },
];

/**
 * Which answers each step must have before "Continue" — mirrors the API's
 * submission check. National ID vs Passport Number depends on the declared
 * nationality, so that slot is added dynamically in `requiredForStep`.
 */
const REQUIRED_BY_STEP: AnswerKey[][] = [
  ['firstName', 'lastName', 'gender', 'dateOfBirth', 'maritalStatus', 'nationality'],
  ['placeOfBirth', 'department', 'tier', 'locationId', 'hairColor', 'eyeColor', 'heightCm', 'weightKg'],
  ['medicalCertificateNumber', 'medicalIssuerName', 'medicalIssueDate'],
  [],
  ['declarationAccepted'],
];

/** Ethiopians must give a National ID; everyone else must give a Passport Number instead. */
function requiredForStep(index: number, nationality: string | null | undefined): AnswerKey[] {
  const base = REQUIRED_BY_STEP[index] ?? [];
  if (index !== 0) return base;
  return [...base, isEthiopianNationality(nationality) ? 'nationalIdNumber' : 'passportNumber'];
}

const ANSWER_KEYS = Object.keys(SEAFARER_REGISTRATION_FIELD_LABELS) as AnswerKey[];

function answersOf(registration: SeafarerRegistration): SaveSeafarerRegistration {
  return Object.fromEntries(ANSWER_KEYS.map((k) => [k, registration[k]])) as SaveSeafarerRegistration;
}

/** Today as `yyyy-mm-dd` in the browser's own zone — en-CA is that format. */
function todayDate(): string {
  return new Date().toLocaleDateString('en-CA');
}

function blank(value: unknown): boolean {
  return value === null || value === undefined || value === '' || value === false;
}

/**
 * Fills blank answers from the profile.
 *
 * The API prefills a draft when it is opened, but an applicant who declared
 * "seafarer" at onboarding lands here before their profile has an address —
 * so whatever they fill in on /profile afterwards would never reach a draft
 * already open. Blank-only: an answer the applicant typed or the server saved
 * is left alone.
 */
function withProfileDefaults(
  answers: SaveSeafarerRegistration,
  profile: CurrentProfile | undefined,
  accountName: string | undefined,
): SaveSeafarerRegistration {
  if (!profile) return answers;
  const a = profile.address;
  const parts = accountName ? splitPersonName(accountName) : null;
  const defaults: SaveSeafarerRegistration = {
    firstName: profile.firstName || parts?.firstName || null,
    middleName: profile.middleName || parts?.middleName || null,
    lastName: profile.lastName || parts?.lastName || null,
    gender: (profile.gender as SaveSeafarerRegistration['gender']) || null,
    dateOfBirth: profile.dob ? profile.dob.slice(0, 10) : null,
    maritalStatus: (profile.maritalStatus as SaveSeafarerRegistration['maritalStatus']) || null,
    placeOfBirth: profile.pob || null,
    nationality: a?.nationality || null,
    nationalIdNumber: a?.idType === 'NID' ? a.idNumber || null : null,
    passportNumber: a?.passportNumber || null,
    passportExpiry: a?.passportExpiry || null,
    permanentAddress: a?.streetAddress || null,
    currentAddress: a?.currentAddress || null,
    emergencyContactName: a?.emergencyContactName || null,
    emergencyContactPhone: a?.emergencyContactPhone || null,
    emergencyContactRelationship: a?.emergencyContactRelation || null,
    department: profile.seafarerDepartment || null,
    // Deepest saved level the picker's maxDepth of 3 can show.
    locationId: a?.subCityId || a?.cityId || a?.regionId || null,
    hairColor: (profile.hairColor as SaveSeafarerRegistration['hairColor']) || null,
    eyeColor: (profile.eyeColor as SaveSeafarerRegistration['eyeColor']) || null,
    bloodType: (profile.bloodType as SaveSeafarerRegistration['bloodType']) || null,
    heightCm: profile.heightCm ?? null,
  };
  const next = { ...answers };
  for (const [key, value] of Object.entries(defaults) as [AnswerKey, unknown][]) {
    if (blank(next[key]) && !blank(value)) (next as Record<string, unknown>)[key] = value;
  }
  return next;
}

/**
 * Seafarer registration — a fixed five-step form, not a configured wizard.
 *
 * A draft is opened on first visit so uploads have an owner and nothing is
 * lost if the browser closes mid-way. Each "Continue" validates the step and
 * saves it; Submit saves everything and asks the API, which names anything
 * still missing. A submitted registration opens to a read-only summary.
 */
export function SeafarerRegistrationPage() {
  const navigate = useNavigate();
  const accountUser = useAppSelector((state) => state.auth.user);
  const { profile } = useCurrentProfile();
  const { data, isLoading } = useGetMySeafarerRegistrationQuery();
  const registration = data?.registration ?? null;

  const [start] = useStartSeafarerRegistrationMutation();
  const [cancelDraft, { isLoading: cancelling }] = useCancelSeafarerRegistrationMutation();
  const [save, { isLoading: saving }] = useSaveSeafarerRegistrationMutation();
  const [submit, { isLoading: submitting }] = useSubmitSeafarerRegistrationMutation();
  const [startError, setStartError] = useState<string | null>(null);
  const [confirmingCancel, setConfirmingCancel] = useState(false);
  const started = useRef(false);

  useEffect(() => {
    if (isLoading || registration || started.current) return;
    started.current = true;
    start()
      .unwrap()
      .catch((err) => setStartError(extractErrorMessage(err)));
  }, [isLoading, registration, start]);

  const { data: attachments = [], refetch: refetchAttachments } = useGetAttachmentsQuery(
    { ownerType: 'SEAFARER_REGISTRATION', ownerId: registration?.id ?? '' },
    { skip: !registration },
  );

  // What a registration must supply is configuration — the same requirement
  // rows the backoffice edits and the server validates against — so the wizard
  // reads them rather than carrying its own copy of the list.
  const { data: registrationConfig } = useGetLicenseTypeRequirementsQuery({
    idOrKey: 'SEAFARER_REGISTRATION',
  });
  const documentRequirements = useMemo(
    () => registrationConfig?.documentRequirements ?? [],
    [registrationConfig],
  );


  const [active, setActive] = useState(0);
  const [viewingSummary, setViewingSummary] = useState(true);
  const [form, setForm] = useState<SaveSeafarerRegistration>({});
  const [errors, setErrors] = useState<Partial<Record<AnswerKey, string>>>({});
  const [issues, setIssues] = useState<ValidationIssue[]>([]);

  /**
   * Fills the document slots from the applicant's own vault the first time
   * they reach that step — a passport already kept under My Documents should
   * not be asked for a second time. The server only fills empty slots.
   */
  const [fillFromVault] = useFillRegistrationDocumentsFromVaultMutation();
  const filledForRegistration = useRef<string | null>(null);
  useEffect(() => {
    const id = registration?.id;
    const editable =
      registration?.status === 'DRAFT' ||
      registration?.status === 'RESUBMIT_REQUIRED';
    if (active !== 3 || !id || !editable) return;
    if (filledForRegistration.current === id) return;
    filledForRegistration.current = id;

    fillFromVault(id)
      .unwrap()
      .then((result) => {
        if (result.filled.length) refetchAttachments();
      })
      // Nothing to fill, or a vault that cannot be read: the applicant uploads
      // by hand, exactly as before.
      .catch(() => undefined);
  }, [active, registration?.id, registration?.status, fillFromVault, refetchAttachments]);

  const accountName = accountUser?.name?.en ?? profile?.user?.name?.en;
  const isDraft = registration?.status === 'DRAFT';

  // Seed local edits from the server copy when the registration (or its
  // round) changes — not on every refetch, which would wipe typing in progress.
  useEffect(() => {
    if (!registration) return;
    const answers = answersOf(registration);
    setForm(isDraft ? withProfileDefaults(answers, profile, accountName) : answers);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [registration?.id, registration?.status]);

  // The profile can arrive after the draft did; fill what is still blank.
  useEffect(() => {
    if (!isDraft || !profile) return;
    setForm((prev) => withProfileDefaults(prev, profile, accountName));
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [profile?.id, profile?.address?.id, isDraft]);

  if (startError) {
    return (
      <Container size="md" py="xl">
        <Alert color={profile?.seafarerNumber ? 'teal' : 'red'} icon={<IconInfoCircle size={16} />} title="Seafarer Registration">
          {profile?.seafarerNumber
            ? `You are already registered as a seafarer (${profile.seafarerNumber}).`
            : startError}
        </Alert>
      </Container>
    );
  }

  if (isLoading || !registration) {
    return (
      <Center h={400}>
        <Loader />
      </Center>
    );
  }

  const isAdjusting = registration.status === 'RESUBMIT_REQUIRED';
  const readOnly = !['DRAFT', 'RESUBMIT_REQUIRED'].includes(registration.status);
  const showSummary = registration.status !== 'DRAFT' && viewingSummary;

  function set(key: AnswerKey, value: unknown) {
    setForm((prev) => ({ ...prev, [key]: value }));
    // The submit refusal names what was wrong at the time it was refused.
    // Leaving it on screen while the applicant corrects it reads as the
    // correction having been ignored.
    if (issues.length) setIssues([]);
    setErrors((prev) => {
      if (!prev[key]) return prev;
      const next = { ...prev };
      delete next[key];
      return next;
    });
  }

  function validateStep(index: number): boolean {
    const found: Partial<Record<AnswerKey, string>> = {};
    for (const key of requiredForStep(index, form.nationality)) {
      if (blank(form[key])) found[key] = `${SEAFARER_REGISTRATION_FIELD_LABELS[key]} is required.`;
    }
    if (index === 1) {
      const { heightCm, weightKg } = PHYSICAL_BOUNDS;
      if (typeof form.heightCm === 'number' && (form.heightCm < heightCm.min || form.heightCm > heightCm.max)) {
        found.heightCm = `Enter a height between ${heightCm.min} and ${heightCm.max} cm.`;
      }
      if (typeof form.weightKg === 'number' && (form.weightKg < weightKg.min || form.weightKg > weightKg.max)) {
        found.weightKg = `Enter a weight between ${weightKg.min} and ${weightKg.max} kg.`;
      }
    }
    if (index === 2 && (form.medicalIssueDate ?? '').slice(0, 10) > todayDate()) {
      found.medicalIssueDate = 'The issue date cannot be in the future.';
    }
    setErrors(found);
    const missingKeys = Object.keys(found) as AnswerKey[];
    if (missingKeys.length) {
      // Name the fields rather than counting them. "Complete 3 required fields"
      // sends the applicant hunting up a step they have already scrolled past;
      // the labels are what let them go straight to it.
      const names = missingKeys.map((k) => SEAFARER_REGISTRATION_FIELD_LABELS[k]);
      notifications.show({
        color: 'red',
        title: missingKeys.length > 1 ? 'Some details are missing' : 'One detail is missing',
        message: `${names.join(', ')}.`,
      });
      return false;
    }
    if (index === 3) {
      const supplied = new Set(attachments.filter((a) => a.files?.length).map((a) => a.documentKey));
      const missing = documentSlots(documentRequirements, form as Record<string, unknown>)
        .filter((d) => d.isRequired && !supplied.has(d.key))
        .map((d) => d.name?.en ?? d.key);
      if (missing.length) {
        notifications.show({
          color: 'red',
          title: 'Documents missing',
          message: `Upload: ${missing.join(', ')}.`,
        });
        return false;
      }
    }
    return true;
  }

  async function saveAnswers(): Promise<boolean> {
    if (readOnly || !registration) return true;
    try {
      await save({ id: registration.id, body: form }).unwrap();
      return true;
    } catch (err) {
      notifications.show({ color: 'red', title: 'Could not save', message: extractErrorMessage(err) });
      return false;
    }
  }

  async function goToStep(target: number) {
    // Saved before anything can turn the navigation around. `form` is the only
    // copy of what was typed, so validating first — as this used to — threw the
    // edit away on every blocked step and every step back: an applicant fixing
    // a field the submit check rejected watched the correction vanish. A draft
    // takes any subset of the answers, so persisting an incomplete one is safe.
    const saved = await saveAnswers();
    if (target > active) {
      if (!saved) return;
      // Going forward validates every step passed over, so a jump cannot skip a
      // required field; the walk stops on the first step that fails.
      for (let step = active; step < target; step++) {
        if (!readOnly && !validateStep(step)) {
          setActive(step);
          return;
        }
      }
    }
    setErrors({});
    setActive(target);
  }

  async function handleSubmit() {
    if (!registration) return;
    setIssues([]);
    if (!(await saveAnswers())) return;
    if (!readOnly && !validateStep(4)) return;
    try {
      await submit(registration.id).unwrap();
      notifications.show({
        color: 'teal',
        title: isAdjusting ? 'Resubmitted' : 'Registration submitted',
        message: isAdjusting
          ? 'Your corrections were sent back to the reviewing officer.'
          : 'You will be notified as it progresses.',
      });
      setViewingSummary(true);
    } catch (err) {
      const found = extractValidationIssues(err);
      setIssues(found);
      notifications.show({
        color: 'red',
        title: 'Registration incomplete',
        message: found.length ? `${found.length} item(s) still need attention.` : extractErrorMessage(err),
      });
    }
  }

  async function handleCancel() {
    if (!registration) return;
    try {
      await cancelDraft(registration.id).unwrap();
      notifications.show({ color: 'teal', title: 'Draft discarded', message: 'Nothing was saved.' });
      navigate('/dashboard');
    } catch (err) {
      notifications.show({ color: 'red', title: 'Could not discard the draft', message: extractErrorMessage(err) });
    } finally {
      setConfirmingCancel(false);
    }
  }

  const stepProps = { form, set, errors, disabled: readOnly };

  return (
    <Container size="lg" py="md">
      <Group justify="space-between" mb="xs" align="flex-start">
        <div>
          <Title order={3}>Seafarer Registration</Title>
          <Group gap="xs" mt={4}>
            <Text size="sm" c="dimmed">
              {registration.registrationNumber}
            </Text>
            <StatusBadge
              tone={SEAFARER_REGISTRATION_STATUS_TONES[registration.status]}
              label={SEAFARER_REGISTRATION_STATUS_LABELS[registration.status]}
            />
          </Group>
        </div>
        <Group gap="xs">
          {registration.status === 'DRAFT' && (
            <Button
              size="xs"
              variant="subtle"
              color="red"
              leftSection={<IconTrash size={14} />}
              onClick={() => setConfirmingCancel(true)}
            >
              Cancel &amp; discard draft
            </Button>
          )}
          {showSummary && !readOnly && (
            <Button size="xs" variant="default" leftSection={<IconPencil size={14} />} onClick={() => setViewingSummary(false)}>
              Edit details
            </Button>
          )}
        </Group>
      </Group>

      {registration.status === 'APPROVED' && (
        <Alert color="teal" icon={<IconCheck size={16} />} title="Registered" mb="md">
          You are a registered seafarer. Your seafarer number is <b>{registration.seafarerNumber}</b>.
          You can now apply for a certificate endorsement from the Endorsement Seafarer page.
        </Alert>
      )}
      {registration.status === 'REJECTED' && (
        <Alert color="red" icon={<IconAlertTriangle size={16} />} title="Registration rejected" mb="md">
          {registration.rejectionReason}
        </Alert>
      )}
      {isAdjusting && (
        <Alert color="orange" icon={<IconAlertTriangle size={16} />} title="Corrections requested" mb="md">
          {registration.reviewRemark}
        </Alert>
      )}
      {registration.status === 'SUBMITTED' && (
        <Alert color="blue" icon={<IconInfoCircle size={16} />} title="Submitted" mb="md">
          Your registration is with the Authority for review. You will be notified of the outcome, or
          asked for corrections if anything is missing.
        </Alert>
      )}
      {issues.length > 0 && (
        <Alert color="red" icon={<IconAlertTriangle size={16} />} title="Still missing" mb="md">
          <Stack gap={2}>
            {issues.map((issue, i) => (
              <Text size="sm" key={i}>
                • {issue.message}
              </Text>
            ))}
          </Stack>
        </Alert>
      )}

      {showSummary && (
        <Paper withBorder p="lg" radius="md">
          <RegistrationSummary
            answers={answersOf(registration)}
            attachments={attachments}
            requirements={documentRequirements}
          />
        </Paper>
      )}

      {!showSummary && (
        <Paper withBorder p="lg" radius="md">
          <Stepper active={active} onStepClick={goToStep} size="sm" mb="lg">
            {STEPS.map((step) => (
              <Stepper.Step key={step.label} label={step.label} description={step.description} />
            ))}
          </Stepper>

          {active === 0 && (
            <IdentityDetailsStep
              {...stepProps}
              account={{ email: accountUser?.email, phoneNumber: accountUser?.phoneNumber }}
            />
          )}
          {active === 1 && <ApplicantDetailsStep {...stepProps} />}
          {active === 2 && <EmergencyContactStep {...stepProps} />}
          {active === 3 && (
            <RegistrationDocuments
              registrationId={registration.id}
              requirements={documentRequirements}
              answers={form as Record<string, unknown>}
              attachments={attachments}
              readOnly={readOnly}
              onUploaded={refetchAttachments}
            />
          )}
          {active === 4 && (
            <Stack>
              <Text fw={600} fz="sm" tt="uppercase" c="gray.6">
                Declaration
              </Text>
              <Grid>
                <CheckboxField
                  {...stepProps}
                  name="declarationAccepted"
                  label="I declare that the information provided is complete and accurate."
                  required
                />
              </Grid>
              <Divider my="md" />
              <Title order={5}>Review</Title>
              <RegistrationSummary
              answers={form}
              attachments={attachments}
              requirements={documentRequirements}
            />
            </Stack>
          )}

          <Group justify="space-between" mt="xl">
            <Button variant="default" onClick={() => goToStep(Math.max(0, active - 1))} disabled={active === 0}>
              Back
            </Button>
            {active < STEPS.length - 1 ? (
              <Button loading={saving} onClick={() => goToStep(active + 1)}>
                Continue
              </Button>
            ) : (
              <Button color="teal" loading={saving || submitting} disabled={readOnly} onClick={handleSubmit}>
                {isAdjusting ? 'Resubmit corrections' : 'Submit registration'}
              </Button>
            )}
          </Group>
        </Paper>
      )}

      <Modal opened={confirmingCancel} onClose={() => setConfirmingCancel(false)} title="Discard this draft?" centered>
        <Stack>
          <Text size="sm">
            Everything you have entered will be deleted, including any documents already uploaded. This cannot be
            undone. You can start a new registration at any time.
          </Text>
          <Group justify="flex-end">
            <Button variant="default" onClick={() => setConfirmingCancel(false)} disabled={cancelling}>
              Keep draft
            </Button>
            <Button color="red" loading={cancelling} onClick={handleCancel}>
              Discard draft
            </Button>
          </Group>
        </Stack>
      </Modal>
    </Container>
  );
}

export default SeafarerRegistrationPage;
