import { useEffect, useMemo, useRef, useState } from 'react';
import {
  ActionIcon,
  Badge,
  Button,
  Card,
  Code,
  Container,
  Group,
  Modal,
  NumberInput,
  Paper,
  ScrollArea,
  Select,
  Stack,
  Switch,
  Text,
  TextInput,
  Textarea,
  Title,
  Tooltip,
} from '@mantine/core';
import {
  IconAlertCircle,
  IconDeviceFloppy,
  IconEye,
  IconPlus,
  IconRosetteDiscountCheck,
  IconTrash,
} from '@tabler/icons-react';
import { notifications } from '@mantine/notifications';
import { useTranslation } from 'react-i18next';
import {
  extractErrorMessage,
  useArchiveLicenseTemplateMutation,
  useCreateLicenseTemplateMutation,
  useDeleteLicenseTemplateMutation,
  useGetBuiltInTemplateQuery,
  useGetLicenseTemplatesQuery,
  useGetLicenseTypesQuery,
  useGetTemplateVariablesQuery,
  usePublishLicenseTemplateMutation,
  useUpdateLicenseValidityMutation,
  useUpdateLicenseTemplateMutation,
  type LicenseTemplate,
} from '@ema-platform/api';
import { EmptyState, ErrorState, ModalFooter, PageHeader } from '@ema-platform/ui';
import { authStorage, usePermissions } from '@ema-platform/auth';
import { PERMISSIONS } from '../../../layouts/nav-config';

/** Same resolution the shared RTK Query baseQuery uses. */
const API_BASE_URL =
  (import.meta as { env?: Record<string, string> }).env?.['VITE_BASE_API_URL'] ??
  'http://localhost:3000/api';

const STATUS_COLOR: Record<LicenseTemplate['status'], string> = {
  DRAFT: 'gray',
  PUBLISHED: 'teal',
  ARCHIVED: 'dark',
};

/**
 * Where the authority designs the certificate its licensees receive.
 *
 * The layout used to be a Handlebars file inside the deployed image, so any
 * change to the authority's own certificate needed a developer and a release.
 * Here it is data: staff author a version, preview the real PDF, and publish.
 * Publishing archives the incumbent, so exactly one design is live per licence
 * type and previously issued certificates keep the design they were made from.
 */
export function CertificateDesignerPage() {
  const { t } = useTranslation();
  const { can } = usePermissions();

  const canEdit = can([PERMISSIONS.UPDATE_TEMPLATE]);
  const canPublish = can([PERMISSIONS.PUBLISH_TEMPLATE]);

  const { data: licenseTypes } = useGetLicenseTypesQuery();
  const [typeId, setTypeId] = useState<string | null>(null);

  const {
    data: templates = [],
    isLoading,
    isError,
    error,
    refetch,
  } = useGetLicenseTemplatesQuery(typeId ?? undefined, { skip: !typeId });
  const { data: variables = [] } = useGetTemplateVariablesQuery();
  const { data: builtIn } = useGetBuiltInTemplateQuery();

  const [createTemplate, { isLoading: creating }] = useCreateLicenseTemplateMutation();
  const [updateTemplate, { isLoading: saving }] = useUpdateLicenseTemplateMutation();
  const [publishTemplate, { isLoading: publishing }] = usePublishLicenseTemplateMutation();
  const [archiveTemplate] = useArchiveLicenseTemplateMutation();
  const [deleteTemplate] = useDeleteLicenseTemplateMutation();
  const [updateValidity, { isLoading: savingValidity }] = useUpdateLicenseValidityMutation();

  const [selectedId, setSelectedId] = useState<string | null>(null);
  const [source, setSource] = useState('');
  const [name, setName] = useState('');
  const [landscape, setLandscape] = useState(true);
  const [newOpen, setNewOpen] = useState(false);
  const [newName, setNewName] = useState('');
  const editorRef = useRef<HTMLTextAreaElement>(null);

  const selectedType = licenseTypes?.items?.find((type) => type.id === typeId);
  const [validityMonths, setValidityMonths] = useState<number>(12);

  // Default to the first licence type so the page is never an empty shell.
  useEffect(() => {
    if (!typeId && licenseTypes?.items?.length) setTypeId(licenseTypes.items[0].id);
  }, [licenseTypes, typeId]);

  useEffect(() => {
    if (selectedType) setValidityMonths(selectedType.validityMonths ?? 12);
  }, [selectedType]);

  const selected = useMemo(
    () => templates.find((tpl) => tpl.id === selectedId) ?? null,
    [templates, selectedId],
  );

  // Pick the live design by default — that is the one staff usually want.
  useEffect(() => {
    if (!templates.length) {
      setSelectedId(null);
      return;
    }
    if (selectedId && templates.some((tpl) => tpl.id === selectedId)) return;
    const published = templates.find((tpl) => tpl.status === 'PUBLISHED');
    setSelectedId((published ?? templates[0]).id);
  }, [templates, selectedId]);

  useEffect(() => {
    if (!selected) return;
    setSource(selected.hbsSource);
    setName(selected.name);
    setLandscape(selected.pageOptions?.landscape ?? true);
  }, [selected]);

  const isPublished = selected?.status === 'PUBLISHED';
  const dirty =
    Boolean(selected) &&
    (source !== selected?.hbsSource ||
      name !== selected?.name ||
      landscape !== (selected?.pageOptions?.landscape ?? true));

  async function run(action: () => Promise<unknown>, success: string) {
    try {
      await action();
      notifications.show({ color: 'teal', title: success, message: '' });
    } catch (err) {
      notifications.show({
        color: 'red',
        title: t('designer.actionFailed', 'Action failed'),
        message: extractErrorMessage(err),
      });
    }
  }

  /** Inserts a placeholder where the caret is, rather than at the end. */
  function insertVariable(key: string) {
    const el = editorRef.current;
    const token = `{{${key}}}`;
    if (!el) {
      setSource((prev) => prev + token);
      return;
    }
    const start = el.selectionStart ?? source.length;
    const end = el.selectionEnd ?? start;
    setSource(source.slice(0, start) + token + source.slice(end));
    requestAnimationFrame(() => {
      el.focus();
      el.setSelectionRange(start + token.length, start + token.length);
    });
  }

  /**
   * Renders the editor's current contents, not the saved row, so unsaved edits
   * are what you see. Opened as a blob so it never leaves a file behind.
   */
  async function preview() {
    try {
      // The preview returns a PDF stream, not JSON, so it bypasses RTK Query
      // and calls the API directly — which means spelling out the base URL and
      // the bearer token that the shared baseQuery would normally attach.
      const token = authStorage.getToken();
      const response = await fetch(`${API_BASE_URL}/license-templates/preview`, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          ...(token ? { Authorization: `Bearer ${token}` } : {}),
        },
        body: JSON.stringify({
          hbsSource: source,
          licenseTypeId: typeId,
          pageOptions: { format: 'A4', landscape, printBackground: true },
        }),
      });
      if (!response.ok) throw new Error(await response.text());
      const url = URL.createObjectURL(await response.blob());
      window.open(url, '_blank', 'noopener');
      // Give the new tab time to read it before revoking.
      setTimeout(() => URL.revokeObjectURL(url), 60_000);
    } catch (err) {
      notifications.show({
        color: 'red',
        title: t('designer.previewFailed', 'Could not render the preview'),
        message: extractErrorMessage(err),
      });
    }
  }

  return (
    <Container size="xl" py="md">
      <PageHeader
        title={t('designer.title', 'Certificate designer')}
        subtitle={t(
          'designer.subtitle',
          'Design the certificate issued to licence holders, and set how long it stays valid.',
        )}
      />

      <Group align="flex-end" mb="md" gap="sm">
        <Select
          label={t('designer.licenceType', 'Licence type')}
          data={(licenseTypes?.items ?? []).map((type) => ({
            value: type.id,
            label: type.name?.en ?? type.key,
          }))}
          value={typeId}
          onChange={(value) => {
            setTypeId(value);
            setSelectedId(null);
          }}
          w={280}
        />

        {/* Validity lives beside the design because it is the other half of
            what a certificate promises. */}
        <NumberInput
          label={t('designer.validityYears', 'Valid for (years)')}
          description={t('designer.validityHint', 'Applied when a licence is issued')}
          value={Number((validityMonths / 12).toFixed(2))}
          onChange={(value) => setValidityMonths(Math.round(Number(value || 0) * 12))}
          min={0.5}
          max={20}
          step={0.5}
          decimalScale={1}
          w={190}
          disabled={!canEdit}
        />
        <Tooltip
          label={
            canEdit
              ? t('designer.saveValidity', 'Save validity')
              : t('designer.noPermission', 'You do not have permission')
          }
        >
          <span>
            <Button
              variant="light"
              loading={savingValidity}
              disabled={!canEdit || !typeId || validityMonths === selectedType?.validityMonths}
              onClick={() =>
                run(
                  () => updateValidity({ id: typeId as string, validityMonths }).unwrap(),
                  t('designer.validitySaved', 'Validity updated'),
                )
              }
            >
              {t('designer.saveValidity', 'Save validity')}
            </Button>
          </span>
        </Tooltip>

        <div style={{ flex: 1 }} />

        <Button
          leftSection={<IconPlus size={16} />}
          disabled={!canEdit || !typeId}
          onClick={() => {
            setNewName(
              `${selectedType?.name?.en ?? 'Certificate'} v${(templates[0]?.version ?? 0) + 1}`,
            );
            setNewOpen(true);
          }}
        >
          {t('designer.newVersion', 'New version')}
        </Button>
      </Group>

      {isError ? (
        <ErrorState
          title={t('designer.loadFailed', 'Could not load the designs')}
          description={extractErrorMessage(error)}
          onRetry={() => refetch()}
          icon={IconAlertCircle}
        />
      ) : !isLoading && templates.length === 0 ? (
        <EmptyState
          title={t('designer.empty', 'No design yet for this licence type')}
          description={t(
            'designer.emptyBody',
            'Certificates currently use the built-in layout. Create a version to take control of it.',
          )}
          action={
            canEdit
              ? {
                  label: t('designer.newVersion', 'New version'),
                  onClick: () => {
                    setNewName(`${selectedType?.name?.en ?? 'Certificate'} v1`);
                    setNewOpen(true);
                  },
                }
              : undefined
          }
        />
      ) : (
        <Group align="flex-start" gap="md" wrap="nowrap">
          {/* Versions */}
          <Stack gap="xs" w={240} style={{ flexShrink: 0 }}>
            <Text fw={600} size="sm">
              {t('designer.versions', 'Versions')}
            </Text>
            {templates.map((tpl) => (
              <Card
                key={tpl.id}
                withBorder
                padding="xs"
                onClick={() => setSelectedId(tpl.id)}
                style={{
                  cursor: 'pointer',
                  borderColor:
                    tpl.id === selectedId ? 'var(--mantine-color-blue-5)' : undefined,
                }}
              >
                <Group justify="space-between" wrap="nowrap">
                  <div style={{ minWidth: 0 }}>
                    <Text size="sm" fw={500} truncate>
                      {tpl.name}
                    </Text>
                    <Text size="xs" c="dimmed">
                      v{tpl.version}
                    </Text>
                  </div>
                  <Badge size="xs" variant="light" color={STATUS_COLOR[tpl.status]}>
                    {tpl.status}
                  </Badge>
                </Group>
              </Card>
            ))}
          </Stack>

          {/* Editor */}
          <Stack gap="sm" style={{ flex: 1, minWidth: 0 }}>
            <Group gap="sm" align="flex-end">
              <TextInput
                label={t('designer.name', 'Version name')}
                value={name}
                onChange={(e) => setName(e.currentTarget.value)}
                disabled={!canEdit || isPublished}
                style={{ flex: 1 }}
              />
              <Switch
                label={t('designer.landscape', 'Landscape')}
                checked={landscape}
                onChange={(e) => setLandscape(e.currentTarget.checked)}
                disabled={!canEdit || isPublished}
              />
            </Group>

            {isPublished && (
              <Paper withBorder p="xs" bg="var(--mantine-color-teal-light)">
                <Text size="xs">
                  {t(
                    'designer.publishedLocked',
                    'This version is live and cannot be edited — certificates have been issued from it. Create a new version to make changes.',
                  )}
                </Text>
              </Paper>
            )}

            <Textarea
              ref={editorRef}
              label={t('designer.source', 'Template (Handlebars + HTML)')}
              value={source}
              onChange={(e) => setSource(e.currentTarget.value)}
              disabled={!canEdit || isPublished}
              autosize
              minRows={18}
              maxRows={30}
              styles={{ input: { fontFamily: 'monospace', fontSize: 12 } }}
            />

            <Group>
              <Button
                variant="light"
                leftSection={<IconEye size={16} />}
                onClick={preview}
                disabled={!source.trim()}
              >
                {t('designer.preview', 'Preview PDF')}
              </Button>
              <Button
                leftSection={<IconDeviceFloppy size={16} />}
                loading={saving}
                disabled={!canEdit || isPublished || !dirty}
                onClick={() =>
                  run(
                    () =>
                      updateTemplate({
                        id: selected!.id,
                        name,
                        hbsSource: source,
                        pageOptions: { format: 'A4', landscape, printBackground: true },
                      }).unwrap(),
                    t('designer.saved', 'Draft saved'),
                  )
                }
              >
                {t('designer.save', 'Save draft')}
              </Button>
              <Tooltip
                label={
                  !canPublish
                    ? t('designer.noPublishPermission', 'You cannot publish designs')
                    : dirty
                      ? t('designer.saveFirst', 'Save your changes first')
                      : t('designer.publishHint', 'Makes this the live certificate design')
                }
              >
                <span>
                  <Button
                    color="teal"
                    leftSection={<IconRosetteDiscountCheck size={16} />}
                    loading={publishing}
                    disabled={!canPublish || isPublished || dirty || !selected}
                    onClick={() =>
                      run(
                        () => publishTemplate(selected!.id).unwrap(),
                        t('designer.published', 'Design published'),
                      )
                    }
                  >
                    {t('designer.publish', 'Publish')}
                  </Button>
                </span>
              </Tooltip>
              <div style={{ flex: 1 }} />
              {selected && isPublished && canPublish && (
                <Button
                  variant="subtle"
                  color="orange"
                  onClick={() =>
                    run(
                      () => archiveTemplate(selected.id).unwrap(),
                      t('designer.archived', 'Design withdrawn'),
                    )
                  }
                >
                  {t('designer.archive', 'Withdraw')}
                </Button>
              )}
              {selected && !isPublished && canEdit && (
                <Tooltip label={t('designer.delete', 'Delete draft')}>
                  <ActionIcon
                    variant="subtle"
                    color="red"
                    aria-label={t('designer.delete', 'Delete draft')}
                    onClick={() =>
                      run(
                        () => deleteTemplate(selected.id).unwrap(),
                        t('designer.deleted', 'Draft deleted'),
                      )
                    }
                  >
                    <IconTrash size={16} />
                  </ActionIcon>
                </Tooltip>
              )}
            </Group>
          </Stack>

          {/* Placeholders */}
          <Stack gap="xs" w={230} style={{ flexShrink: 0 }}>
            <Text fw={600} size="sm">
              {t('designer.variables', 'Placeholders')}
            </Text>
            <Text size="xs" c="dimmed">
              {t('designer.variablesHint', 'Click to insert at the cursor.')}
            </Text>
            <ScrollArea.Autosize mah={480} type="hover">
              <Stack gap={4}>
                {variables.map((variable) => (
                  <Tooltip key={variable.key} label={variable.label} position="left">
                    <Button
                      size="compact-xs"
                      variant="default"
                      justify="flex-start"
                      disabled={!canEdit || isPublished}
                      onClick={() => insertVariable(variable.key)}
                    >
                      <Code fz={10}>{`{{${variable.key}}}`}</Code>
                    </Button>
                  </Tooltip>
                ))}
              </Stack>
            </ScrollArea.Autosize>
          </Stack>
        </Group>
      )}

      <Modal
        opened={newOpen}
        onClose={() => setNewOpen(false)}
        title={t('designer.newVersion', 'New version')}
      >
        <Stack>
          <TextInput
            label={t('designer.name', 'Version name')}
            value={newName}
            onChange={(e) => setNewName(e.currentTarget.value)}
            withAsterisk
          />
          <Text size="xs" c="dimmed">
            {t(
              'designer.newHint',
              'Starts from the live design, or the built-in layout if this type has none.',
            )}
          </Text>
          <ModalFooter>
            <Button variant="default" onClick={() => setNewOpen(false)}>
              {t('common.cancel', 'Cancel')}
            </Button>
            <Button
              loading={creating}
              disabled={!newName.trim()}
              onClick={() =>
                run(async () => {
                  const created = await createTemplate({
                    licenseTypeId: typeId as string,
                    name: newName.trim(),
                    hbsSource: templates.length ? undefined : builtIn?.hbsSource,
                  }).unwrap();
                  setSelectedId(created.id);
                  setNewOpen(false);
                }, t('designer.created', 'Draft created'))
              }
            >
              {t('designer.create', 'Create')}
            </Button>
          </ModalFooter>
        </Stack>
      </Modal>
    </Container>
  );
}

export default CertificateDesignerPage;
