import { useEffect, useState } from 'react';
import {
  Alert,
  Button,
  Group,
  MultiSelect,
  NumberInput,
  Paper,
  Select,
  Stack,
  Switch,
  Text,
  TextInput,
  Tooltip,
} from '@mantine/core';
import { IconAlertTriangle, IconDeviceFloppy } from '@tabler/icons-react';
import { useTranslation } from 'react-i18next';
import { LICENSE_PERMISSIONS, usePermissions } from '@ema-platform/auth';
import {
  useUpdateLicenseBehaviorMutation,
  type CertificateCategory,
  type CompletionEffect,
  type LicenseType,
  type ServiceKind,
  type WorkflowProfile,
} from '@ema-platform/api';
import { useRequirementActions } from '../hooks/useRequirementActions';

/** The shape the form edits — every field the behaviour endpoint accepts. */
interface Draft {
  workflowProfile: WorkflowProfile;
  serviceKind: ServiceKind;
  completionEffect: CompletionEffect | null;
  certificateCategory: CertificateCategory | null;
  requiresExamination: boolean;
  inspectionRequired: boolean;
  issuesCertificate: boolean;
  renewalEnabled: boolean;
  requiresSeafarerRegistration: boolean;
  requiresValidMedical: boolean;
  minSeaTimeDays: number | null;
  capitalThreshold: number | null;
  validityMonths: number;
  validityDays: number | null;
  renewalWindowDays: number;
  expiryReminderDays: number[];
  requiresOperatorMode: boolean;
  allowMultipleOpenDrafts: boolean;
  requiresIssuanceScheduling: boolean;
  uniqueFormKeyPath: string | null;
  slaHours: number | null;
}

/** Offsets EMA reminds on. Fixed rather than free-form — these are policy, not arithmetic. */
const REMINDER_OFFSETS = ['90', '60', '30', '14', '7'];

function toDraft(licenseType: LicenseType): Draft {
  return {
    workflowProfile: licenseType.workflowProfile ?? 'STANDARD',
    serviceKind: licenseType.serviceKind ?? 'LICENSE',
    completionEffect: licenseType.completionEffect ?? null,
    certificateCategory: licenseType.certificateCategory ?? null,
    requiresExamination: licenseType.requiresExamination ?? false,
    inspectionRequired: licenseType.inspectionRequired ?? true,
    issuesCertificate: licenseType.issuesCertificate ?? true,
    renewalEnabled: licenseType.renewalEnabled ?? true,
    requiresSeafarerRegistration:
      licenseType.requiresSeafarerRegistration ?? false,
    requiresValidMedical: licenseType.requiresValidMedical ?? false,
    minSeaTimeDays: licenseType.minSeaTimeDays ?? null,
    capitalThreshold:
      licenseType.capitalThreshold == null
        ? null
        : Number(licenseType.capitalThreshold),
    // Zero is a real stored state for `validityMonths` (a type that issues
    // nothing with an expiry) and is kept. Zero in the other two is not a
    // policy anyone set — it is an unfilled column — and seeding a box with a
    // value below its own floor only produces a save the server rejects.
    validityMonths: licenseType.validityMonths ?? 12,
    validityDays: licenseType.validityDays || null,
    renewalWindowDays: licenseType.renewalWindowDays || 60,
    expiryReminderDays: licenseType.expiryReminderDays ?? [60, 30, 7],
    requiresOperatorMode: licenseType.requiresOperatorMode ?? true,
    allowMultipleOpenDrafts: licenseType.allowMultipleOpenDrafts ?? false,
    requiresIssuanceScheduling: licenseType.requiresIssuanceScheduling ?? false,
    uniqueFormKeyPath: licenseType.uniqueFormKeyPath ?? null,
    slaHours: licenseType.slaHours ?? null,
  };
}

/**
 * How one licence type behaves: the course it runs, who may apply, when it
 * renews and what rules the applicant meets.
 *
 * These were seed-only until now — changing an SLA target or a renewal window
 * meant editing a file and redeploying. They are read live off the licence
 * type rather than snapshotted onto applications, so a change here applies to
 * files already in the queue as well as new ones. For the three settings that
 * decide an application's course the server refuses the change outright while
 * anything is still awaiting a decision, rather than stranding it.
 */
export function BehaviorTab({ licenseType }: { licenseType: LicenseType }) {
  const { t } = useTranslation();
  const run = useRequirementActions();
  const { can } = usePermissions();
  const canEdit = can([LICENSE_PERMISSIONS.UPDATE_LICENSE_TYPE]);

  const [save, { isLoading: saving }] = useUpdateLicenseBehaviorMutation();
  const [draft, setDraft] = useState<Draft>(() => toDraft(licenseType));
  const [dirty, setDirty] = useState(false);

  // Keyed on the id alone, like FormSchemaTab: this tab's own save invalidates
  // the licence-type list, and the refetch that follows must not overwrite an
  // edit the administrator is still working on.
  useEffect(() => {
    setDraft(toDraft(licenseType));
    setDirty(false);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [licenseType.id]);

  function patch(values: Partial<Draft>) {
    setDraft((current) => ({ ...current, ...values }));
    setDirty(true);
  }

  function set<K extends keyof Draft>(key: K, value: Draft[K]) {
    patch({ [key]: value } as Partial<Draft>);
  }

  // Nothing this type issues carries an expiry date — a transfer, or any
  // one-off record. Renewal, its window and its reminders are all measured
  // against an expiry that never arrives, so none of them are asked for.
  const expires = draft.validityDays !== null || draft.validityMonths > 0;

  // The server's floor for a stated term is 6 months, so no-expiry is a state
  // this form can hold and edit around but cannot switch a type into. Offered
  // only where it is already what the type is, rather than as an option whose
  // save would be refused.
  const noExpiryAvailable =
    (licenseType.validityMonths ?? 12) === 0 && !licenseType.validityDays;

  async function onSave() {
    // Only the settings this form actually asked for. The endpoint patches, so
    // an omitted field keeps its stored value — and the fields hidden above are
    // hidden precisely because the type has no such policy, which the server
    // stores as a zero its own validators then refuse (`validityMonths` has a
    // floor of 6, `renewalWindowDays` of 1). Echoing those back is what made
    // saving an ownership transfer fail outright.
    const {
      validityMonths,
      validityDays,
      renewalWindowDays,
      expiryReminderDays,
      ...rest
    } = draft;

    const ok = await run(
      () =>
        save({
          id: licenseType.id,
          ...rest,
          ...(expires ? { validityMonths, validityDays } : {}),
          ...(expires && draft.renewalEnabled
            ? { renewalWindowDays, expiryReminderDays }
            : {}),
        }).unwrap(),
      t('certReq.behavior.saved', 'Configuration saved.'),
    );
    if (ok) setDirty(false);
  }

  return (
    <Stack gap="md">
      <Section title={t('certReq.behavior.workflow', 'Workflow')}>
        <Alert
          variant="light"
          color="yellow"
          icon={<IconAlertTriangle size={16} />}
        >
          <Text size="sm">
            {t(
              'certReq.behavior.workflowWarning',
              'These three settings decide the course an application runs. They cannot be changed while applications of this type are still awaiting a decision — saving will be refused until those are decided.',
            )}
          </Text>
        </Alert>

        <Select
          label={t('certReq.behavior.workflowProfile', 'Workflow profile')}
          description={t(
            'certReq.behavior.workflowProfileHint',
            'REGISTRATION drops the evaluation and inspection stages.',
          )}
          data={[
            { value: 'STANDARD', label: t('certReq.behavior.standard', 'Standard licence course') },
            { value: 'REGISTRATION', label: t('certReq.behavior.registration', 'Registration (review only)') },
          ]}
          value={draft.workflowProfile}
          onChange={(v) => v && set('workflowProfile', v as WorkflowProfile)}
          allowDeselect={false}
          disabled={!canEdit}
        />

        <Select
          label={t('certReq.behavior.completionEffect', 'Completion effect')}
          description={t(
            'certReq.behavior.completionEffectHint',
            'Platform action taken when an application of this type completes.',
          )}
          data={[
            { value: 'REGISTER_SEAFARER', label: t('certReq.behavior.registerSeafarer', 'Register the seafarer') },
            { value: 'REGISTER_VESSEL', label: t('certReq.behavior.registerVessel', 'Register the vessel') },
            { value: 'OPEN_SEAFARER_DOCUMENTS', label: t('certReq.behavior.openDocuments', 'Open seafarer documents') },
          ]}
          value={draft.completionEffect}
          onChange={(v) => set('completionEffect', (v as CompletionEffect) ?? null)}
          placeholder={t('certReq.behavior.noEffect', 'No side effect')}
          clearable
          disabled={!canEdit}
        />

        <Switch
          checked={draft.requiresExamination}
          onChange={(e) => set('requiresExamination', e.currentTarget.checked)}
          label={t('certReq.behavior.requiresExamination', 'Requires an examination')}
          description={t(
            'certReq.behavior.requiresExaminationHint',
            'Routes the application through eligibility, then exam, then certificate. Set the three stage fees on the payment configuration screen.',
          )}
          disabled={!canEdit}
        />

        <Switch
          checked={draft.issuesCertificate}
          onChange={(e) => set('issuesCertificate', e.currentTarget.checked)}
          label={t('certReq.behavior.issuesCertificate', 'Issues a certificate')}
          description={t(
            'certReq.behavior.issuesCertificateHint',
            'Turn off for a type that ends with an EMA decision and never reaches a payment stage. Turning it back on requires a new-application fee to be set, or approved applicants would be asked for a fee that does not exist.',
          )}
          disabled={!canEdit}
        />

        <Select
          label={t('certReq.behavior.serviceKind', 'Service kind')}
          description={t(
            'certReq.behavior.serviceKindHint',
            'Catalogue classification only — no workflow depends on it.',
          )}
          data={[
            { value: 'LICENSE', label: t('certReq.behavior.license', 'Licence') },
            { value: 'REGISTRATION', label: t('certReq.behavior.registrationKind', 'Registration') },
          ]}
          value={draft.serviceKind}
          onChange={(v) => v && set('serviceKind', v as ServiceKind)}
          allowDeselect={false}
          disabled={!canEdit}
        />
      </Section>

      <Section title={t('certReq.behavior.eligibility', 'Eligibility gates')}>
        <Text size="xs" c="dimmed">
          {t(
            'certReq.behavior.eligibilityHint',
            'Checked when an applicant submits, and the capital requirement again when an officer approves. Tightening a gate can stop someone mid-way from submitting a draft they have already started.',
          )}
        </Text>

        <NullableNumber
          label={t('certReq.behavior.capitalThreshold', 'Minimum paid-up capital')}
          switchLabel={t('certReq.behavior.capitalOn', 'Require a minimum paid-up capital')}
          description={t(
            'certReq.behavior.capitalHint',
            'An officer cannot approve until they have verified capital at or above this. Applies to applications already in the queue, not just new ones.',
          )}
          value={draft.capitalThreshold}
          onChange={(v) => set('capitalThreshold', v)}
          disabled={!canEdit}
          min={0}
          defaultValue={1_000_000}
          thousandSeparator
        />

        <Switch
          checked={draft.requiresSeafarerRegistration}
          onChange={(e) => set('requiresSeafarerRegistration', e.currentTarget.checked)}
          label={t('certReq.behavior.requiresSeafarer', 'Requires an active seafarer registration')}
          description={t(
            'certReq.behavior.requiresSeafarerHint',
            'Also marks this type as a seafarer certificate, which is what makes it appear in the seafarer portal.',
          )}
          disabled={!canEdit}
        />

        <Switch
          checked={draft.requiresValidMedical}
          onChange={(e) => set('requiresValidMedical', e.currentTarget.checked)}
          label={t('certReq.behavior.requiresMedical', 'Requires a current medical certificate')}
          disabled={!canEdit}
        />

        <NullableNumber
          label={t('certReq.behavior.minSeaTime', 'Minimum sea time (days)')}
          switchLabel={t('certReq.behavior.minSeaTimeOn', 'Require verified sea time')}
          value={draft.minSeaTimeDays}
          onChange={(v) => set('minSeaTimeDays', v)}
          disabled={!canEdit}
          min={0}
        />

        <Select
          label={t('certReq.behavior.certificateCategory', 'Certificate category')}
          description={t(
            'certReq.behavior.certificateCategoryHint',
            'What kind of document this issues. Endorsements are grouped separately in the seafarer portal.',
          )}
          data={['COC', 'COP', 'ENDORSEMENT', 'GOC', 'NATIONAL'].map((v) => ({
            value: v,
            label: v,
          }))}
          value={draft.certificateCategory}
          onChange={(v) => set('certificateCategory', (v as CertificateCategory) ?? null)}
          placeholder={t('certReq.behavior.notACertificate', 'Not a certificate')}
          clearable
          disabled={!canEdit}
        />
      </Section>

      <Section title={t('certReq.behavior.renewal', 'Validity and renewal')}>
        {/* Amount + unit rather than a years box that silently divides by 12:
            the seed says `validityMonths: 12` and this now says "12 Months",
            so the two read the same. Months advance the calendar (issued on
            the 31st, expires on the 31st); days are for terms shorter than a
            month can express. The third unit is no term at all, which the
            server stores as `validityMonths: 0`. */}
        <Group align="flex-end" gap="sm" wrap="nowrap">
          {expires && (
            <NumberInput
              label={t('certReq.behavior.validity', 'Valid for')}
              description={t(
                'certReq.behavior.validityHint',
                'Applied when a licence is issued. Licences already issued keep the expiry date they were given.',
              )}
              value={draft.validityDays ?? draft.validityMonths}
              onChange={(v) => {
                const next = typeof v === 'number' ? v : 0;
                if (!next) return;
                if (draft.validityDays !== null) set('validityDays', next);
                else set('validityMonths', next);
              }}
              // Matches the server's ranges, so the box cannot offer a value the
              // save would reject: 1–3650 days, or 6–240 months.
              min={draft.validityDays !== null ? 1 : 6}
              max={draft.validityDays !== null ? 3650 : 240}
              allowNegative={false}
              disabled={!canEdit}
              flex={1}
            />
          )}
          <Select
            label={
              expires
                ? undefined
                : t('certReq.behavior.validityUnit', 'Validity unit')
            }
            aria-label={t('certReq.behavior.validityUnit', 'Validity unit')}
            data={[
              { value: 'MONTHS', label: t('certReq.behavior.unitMonths', 'Months') },
              { value: 'DAYS', label: t('certReq.behavior.unitDays', 'Days') },
              ...(noExpiryAvailable
                ? [
                    {
                      value: 'NONE',
                      label: t('certReq.behavior.unitNone', 'Does not expire'),
                    },
                  ]
                : []),
            ]}
            value={
              draft.validityDays !== null
                ? 'DAYS'
                : draft.validityMonths > 0
                  ? 'MONTHS'
                  : 'NONE'
            }
            onChange={(unit) => {
              // Switching unit is a change of policy, not a conversion: 12
              // calendar months is not 365 days, so carry no arithmetic across
              // and let the administrator state the new term outright.
              if (unit === 'DAYS') set('validityDays', draft.validityDays ?? 90);
              else if (unit === 'MONTHS')
                patch({
                  validityDays: null,
                  validityMonths:
                    draft.validityMonths >= 6 ? draft.validityMonths : 12,
                });
              // No expiry means no renewal policy: clear it here rather than
              // save renewal settings that could never fire.
              else
                patch({
                  validityDays: null,
                  validityMonths: 0,
                  renewalEnabled: false,
                });
            }}
            allowDeselect={false}
            disabled={!canEdit}
            w={expires ? 130 : 220}
          />
        </Group>

        {!expires && (
          <Text size="xs" c="dimmed">
            {t(
              'certReq.behavior.noExpiryHint',
              'What this type issues never expires — an ownership transfer, or any one-off record. There is no renewal policy to set.',
            )}
          </Text>
        )}

        {expires && (
          <>
            <Switch
              checked={draft.renewalEnabled}
              onChange={(e) => set('renewalEnabled', e.currentTarget.checked)}
              label={t('certReq.behavior.renewalEnabled', 'Holders may renew this licence')}
              description={t(
                'certReq.behavior.renewalEnabledHint',
                'Off for one-off issues — a registration that does not lapse, or a waiver written for a single shipment.',
              )}
              disabled={!canEdit}
            />

            {/* The window and the reminders are both measured against an expiry
                a non-renewing licence never reaches, so they are hidden rather
                than shown as settings that quietly do nothing. */}
            {draft.renewalEnabled && (
              <>
                <NumberInput
                  label={t('certReq.behavior.renewalWindow', 'Renewal opens (days before expiry)')}
                  value={draft.renewalWindowDays}
                  onChange={(v) =>
                    set('renewalWindowDays', typeof v === 'number' ? v : draft.renewalWindowDays)
                  }
                  min={1}
                  max={365}
                  allowNegative={false}
                  disabled={!canEdit}
                />

                <MultiSelect
                  label={t('certReq.behavior.reminders', 'Expiry reminders (days before)')}
                  description={t(
                    'certReq.behavior.remindersHint',
                    'The holder is reminded at each of these offsets.',
                  )}
                  data={REMINDER_OFFSETS}
                  value={draft.expiryReminderDays.map(String)}
                  onChange={(values) =>
                    set(
                      'expiryReminderDays',
                      values.map(Number).sort((a, b) => b - a),
                    )
                  }
                  disabled={!canEdit}
                  clearable
                />
              </>
            )}
          </>
        )}
      </Section>

      <Section title={t('certReq.behavior.applicantRules', 'Applicant rules')}>
        <Switch
          checked={draft.requiresOperatorMode}
          onChange={(e) => set('requiresOperatorMode', e.currentTarget.checked)}
          label={t('certReq.behavior.requiresOperatorMode', 'Applicant must declare this operating mode')}
          description={t(
            'certReq.behavior.requiresOperatorModeHint',
            'Turn off for person-centric registrations any signed-in applicant may start.',
          )}
          disabled={!canEdit}
        />

        <Switch
          checked={draft.allowMultipleOpenDrafts}
          onChange={(e) => set('allowMultipleOpenDrafts', e.currentTarget.checked)}
          label={t('certReq.behavior.allowMultipleDrafts', 'Allow several open drafts at once')}
          description={t(
            'certReq.behavior.allowMultipleDraftsHint',
            'On for per-asset registrations — registering a second vessel must not resume the first one’s draft.',
          )}
          disabled={!canEdit}
        />

        <Switch
          checked={draft.inspectionRequired}
          onChange={(e) => set('inspectionRequired', e.currentTarget.checked)}
          label={t('certReq.behavior.inspectionRequired', 'Requires a physical inspection')}
          description={t(
            'certReq.behavior.inspectionRequiredHint',
            'An officer cannot approve until an inspection has been recorded and passed. Safe to change with applications in progress — an officer can still assign an inspector to a file already under evaluation.',
          )}
          disabled={!canEdit}
        />

        <Switch
          checked={draft.requiresIssuanceScheduling}
          onChange={(e) => set('requiresIssuanceScheduling', e.currentTarget.checked)}
          label={t('certReq.behavior.requiresScheduling', 'Schedule a pickup date before issuing')}
          description={t(
            'certReq.behavior.requiresSchedulingHint',
            'For documents printed once and handed over in person.',
          )}
          disabled={!canEdit}
        />

        <NullableNumber
          label={t('certReq.behavior.slaHours', 'Decision target (hours)')}
          switchLabel={t('certReq.behavior.slaOn', 'Track against an SLA')}
          value={draft.slaHours}
          onChange={(v) => set('slaHours', v)}
          disabled={!canEdit}
          min={1}
          description={t(
            'certReq.behavior.slaHint',
            'Drives the Age/SLA column and the Overdue view. Applies to applications already submitted as well as new ones.',
          )}
        />

        <TextInput
          label={t('certReq.behavior.uniqueFormKey', 'One application per answer at')}
          description={t(
            'certReq.behavior.uniqueFormKeyHint',
            'Dotted form path, e.g. shipment.billOfLading. The field must exist in this type’s form, or no application can be submitted.',
          )}
          value={draft.uniqueFormKeyPath ?? ''}
          onChange={(e) =>
            set('uniqueFormKeyPath', e.currentTarget.value.trim() || null)
          }
          maxLength={128}
          placeholder={t('certReq.behavior.noUniqueRule', 'No uniqueness rule')}
          disabled={!canEdit}
        />
      </Section>

      <Group justify="flex-end">
        <Tooltip
          label={t('certReq.behavior.noPermission', 'You do not have permission to change licence configuration.')}
          disabled={canEdit}
        >
          <Button
            leftSection={<IconDeviceFloppy size={16} />}
            onClick={onSave}
            loading={saving}
            disabled={!canEdit || !dirty}
          >
            {t('certReq.behavior.save', 'Save configuration')}
          </Button>
        </Tooltip>
      </Group>
    </Stack>
  );
}

function Section({
  title,
  children,
}: {
  title: string;
  children: React.ReactNode;
}) {
  return (
    <Paper withBorder radius="md" p="md">
      <Stack gap="sm">
        <Text fw={600} size="sm">
          {title}
        </Text>
        {children}
      </Stack>
    </Paper>
  );
}

/**
 * A number that may be switched off entirely.
 *
 * Null is a real configuration state here — "not tracked against an SLA", "no
 * sea-time floor" — and is not the same as an empty box, so the choice gets its
 * own switch rather than being inferred from a blank field.
 */
function NullableNumber({
  label,
  switchLabel,
  description,
  value,
  onChange,
  disabled,
  min,
  defaultValue,
  thousandSeparator,
}: {
  label: string;
  switchLabel: string;
  description?: string;
  value: number | null;
  onChange: (value: number | null) => void;
  disabled: boolean;
  min: number;
  /** Seeded when the switch is turned on. Defaults to the minimum. */
  defaultValue?: number;
  thousandSeparator?: boolean;
}) {
  return (
    <Stack gap="xs">
      <Switch
        checked={value !== null}
        onChange={(e) =>
          onChange(e.currentTarget.checked ? (defaultValue ?? min ?? 1) : null)
        }
        label={switchLabel}
        description={description}
        disabled={disabled}
      />
      {value !== null && (
        <NumberInput
          label={label}
          value={value}
          onChange={(v) => onChange(typeof v === 'number' ? v : value)}
          min={min}
          allowNegative={false}
          thousandSeparator={thousandSeparator ? ',' : undefined}
          decimalScale={thousandSeparator ? 2 : undefined}
          disabled={disabled}
        />
      )}
    </Stack>
  );
}
