import { useMemo, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import {
  Alert,
  Box,
  Button,
  Card,
  Container,
  Group,
  Paper,
  Select,
  SimpleGrid,
  Skeleton,
  Stack,
  Tabs,
  Text,
  TextInput,
  ThemeIcon,
  Title,
} from '@mantine/core';
import {
  IconAlertTriangle,
  IconCertificate,
  IconClipboardList,
  IconClockHour4,
  IconFileText,
  IconPlus,
  IconSearch,
  IconX,
} from '@tabler/icons-react';
import { AdvancedTable, AmharicDatePicker, ConfirmModal, EmptyState, useServerTable } from '@ema-platform/ui';
import { useDateDisplayer } from '@ema-platform/shared';
import { LicenseCatalogue } from '../../components/LicenseCatalogue';
import { LicenseCard, useReissueLicense, useRenewLicense } from '../../components/LicenseCard';
import { useApplicationPayment } from '../../../payments/hooks/useApplicationPayment';
import { notifications } from '@mantine/notifications';
import {
  APPLICANT_ACTION_STATUSES,
  STATUS_COLORS,
  TERMINAL_STATUSES,
  applicantOrCompanyName,
  extractErrorMessage,
  useBypassPaymentMutation,
  useDiscardApplicationMutation,
  useGetCertificateUrlMutation,
  useGetMyApplicationsQuery,
  useGetMyLicensesQuery,
  useGetPaymentCapabilitiesQuery,
  useApiQuery,
  useRetakeExamMutation,
  type ApplicationKind,
  type LicenseApplication,
  type LicenseStatus,
} from '@ema-platform/api';
import {
  LICENSE_PERMISSIONS,
  RequirePermission,
  usePermissions,
} from '@ema-platform/auth';
import { examStageFor, registrationForApplication } from '../../exam-stage';
import type { MyRegistration } from '../../../exams/pages/ExamsPage';
import { applicationColumns } from './columns';
import { applicationActionsColumn } from './actions';
import classes from '../MyApplicationsPage.module.css';

/** Days before expiry at which a licence is worth flagging. */
const EXPIRY_WARNING_DAYS = 60;

function daysUntil(date: string): number {
  const ms = new Date(date).getTime() - Date.now();
  return Math.ceil(ms / 86_400_000);
}

type Bucket = 'needsYou' | 'inProgress' | 'completed' | null;

function bucketOf(status: LicenseStatus): Exclude<Bucket, null> {
  if (APPLICANT_ACTION_STATUSES.includes(status)) return 'needsYou';
  if (TERMINAL_STATUSES.includes(status)) return 'completed';
  return 'inProgress';
}

type Tab = 'applications' | 'licences' | 'apply';
const TABS: Tab[] = ['applications', 'licences', 'apply'];

function tabFromHash(hash: string): Tab {
  const value = hash.replace('#', '');
  return (TABS as string[]).includes(value) ? (value as Tab) : 'applications';
}

/**
 * The applicant's landing page: which licences they can apply for, and the
 * state of anything already filed.
 *
 * Three tabs instead of one long scroll — applications, licences and the
 * catalogue each own their own space, so a returning applicant lands on
 * exactly what they came back to check instead of scrolling past it.
 */
export function MyApplicationsPage() {
  const navigate = useNavigate();
  const { t, i18n } = useTranslation();
  const showDate = useDateDisplayer();
  const { data, isFetching, refetch } = useGetMyApplicationsQuery();
  // The examination leg lives on the registration and the attempt, not on the
  // application: registering, being marked present and sitting the paper all
  // happen there. Polled rather than fetched once, because attendance is
  // recorded by an invigilator while the candidate is sitting in front of this
  // screen — a status that only updates on reload is the defect itself.
  const { data: examRegistrations } = useApiQuery<MyRegistration[]>(
    { url: '/exams/registrations/mine', method: 'GET' },
    { pollingInterval: 30_000, refetchOnFocus: true },
  );
  const { pay, isPaying } = useApplicationPayment();
  const { data: capabilities } = useGetPaymentCapabilitiesQuery();
  const { data: licences, isFetching: isFetchingLicences } = useGetMyLicensesQuery();
  const [bypassPayment, { isLoading: bypassing }] = useBypassPaymentMutation();
  const [retakeExam, { isLoading: requestingExamFee }] = useRetakeExamMutation();
  const [getCertificateUrl] = useGetCertificateUrlMutation();
  const [discardApplication, { isLoading: discarding }] = useDiscardApplicationMutation();
  const [discardTarget, setDiscardTarget] = useState<{ id: string; label: string } | null>(null);
  const { renewLicense, isRenewing } = useRenewLicense();
  const { reissueLicense, isReissuing } = useReissueLicense();
  const [isDownloadingCert, setIsDownloadingCert] = useState(false);
  const { can } = usePermissions();

  const [tab, setTab] = useState<Tab>(() =>
    typeof window !== 'undefined' ? tabFromHash(window.location.hash) : 'applications',
  );

  function changeTab(next: Tab) {
    setTab(next);
    window.history.replaceState(null, '', `#${next}`);
  }

  async function handleBypass(applicationId: string) {
    try {
      const result = await bypassPayment(applicationId).unwrap();
      notifications.show({
        color: 'teal',
        title: 'Payment bypassed',
        message: result.certificateIssued
          ? 'The licence has been issued — see the Licences tab.'
          : `Application is now ${result.status.replace(/_/g, ' ').toLowerCase()}.`,
      });
    } catch (err) {
      notifications.show({
        color: 'red',
        title: 'Bypass failed',
        message: extractErrorMessage(err),
      });
    }
  }

  /** Throws away an unfinished draft, after the applicant confirms. */
  async function handleDiscard() {
    if (!discardTarget) return;
    try {
      await discardApplication(discardTarget.id).unwrap();
      notifications.show({
        color: 'teal',
        title: t('applications.actions.discarded'),
        message: discardTarget.label,
      });
      setDiscardTarget(null);
    } catch (err) {
      notifications.show({
        color: 'red',
        title: t('applications.actions.discardFailed'),
        message: extractErrorMessage(err),
      });
    }
  }

  /**
   * Re-opens the examination fee for a failed candidate.
   *
   * Payment is a separate step: this only moves the application back to
   * EXAM_PAYMENT_PENDING, and the Pay button that then appears hands off to
   * the provider the same way every other fee does.
   */
  async function retakeExamFee(applicationId: string) {
    try {
      await retakeExam(applicationId).unwrap();
      notifications.show({
        color: 'teal',
        title: t('applications.examFeeRequested', 'Exam fee ready'),
        message: t(
          'applications.examFeeRequestedBody',
          'Pay the examination fee and you will be scheduled for a sitting.',
        ),
      });
    } catch (err) {
      notifications.show({
        color: 'red',
        title: t('applications.examFeeFailed', 'Could not book the exam'),
        message: extractErrorMessage(err),
      });
    }
  }

  /**
   * Opens the certificate belonging to an application.
   *
   * The applicant knows their application number, not the licence id, so the
   * licence is looked up from the list already loaded rather than making them
   * find it in a separate table.
   */
  async function openCertificateForApplication(applicationId: string) {
    const licence = (licences?.items ?? []).find((l) => l.applicationId === applicationId);
    if (!licence) {
      notifications.show({
        color: 'yellow',
        title: 'Certificate not ready',
        message:
          'The licence for this application has not been issued yet. It will appear under Licences.',
      });
      return;
    }
    await downloadCertificate(licence.id);
  }

  async function downloadCertificate(licenseId: string) {
    setIsDownloadingCert(true);
    try {
      const { url } = await getCertificateUrl(licenseId).unwrap();
      window.open(url, '_blank', 'noopener');
    } catch (err) {
      notifications.show({
        color: 'red',
        title: 'Could not open the certificate',
        message: extractErrorMessage(err),
      });
    } finally {
      setIsDownloadingCert(false);
    }
  }

  const allItems = data?.items ?? [];
  const licenceItems = licences?.items ?? [];
  const activeLicences = licenceItems.filter((l) => l.status === 'ACTIVE');
  const expiringSoon = activeLicences.filter((l) => {
    const days = l.daysUntilExpiry ?? daysUntil(l.expiryDate);
    return days >= 0 && days <= EXPIRY_WARNING_DAYS;
  });

  const [search, setSearch] = useState('');
  const [statusFilter, setStatusFilter] = useState<LicenseStatus | null>(null);
  const [kindFilter, setKindFilter] = useState<ApplicationKind | null>(null);
  const [dateFrom, setDateFrom] = useState('');
  const [dateTo, setDateTo] = useState('');
  const [bucketFilter, setBucketFilter] = useState<Bucket>(null);
  const hasFilters = Boolean(
    search || statusFilter || kindFilter || dateFrom || dateTo || bucketFilter,
  );
  const { setPageIndex, pageSize, setPageSize, paginate } = useServerTable({ pageSize: 10 });

  const counts = useMemo(() => {
    const result = { needsYou: 0, inProgress: 0, completed: 0 };
    for (const app of allItems) result[bucketOf(app.status)]++;
    return result;
  }, [allItems]);

  const items = useMemo(() => {
    const q = search.trim().toLowerCase();
    const filtered = allItems.filter((app) => {
      if (bucketFilter && bucketOf(app.status) !== bucketFilter) return false;
      if (q) {
        const haystack = `${app.applicationNumber} ${applicantOrCompanyName(app) ?? ''}`.toLowerCase();
        if (!haystack.includes(q)) return false;
      }
      if (statusFilter && app.status !== statusFilter) return false;
      if (kindFilter && app.kind !== kindFilter) return false;
      // Drafts have no submittedAt, so date filtering falls back to createdAt
      // rather than silently excluding every draft from a date-ranged search.
      const at = app.submittedAt ?? app.createdAt;
      if (dateFrom && (!at || at < dateFrom)) return false;
      if (dateTo && (!at || at > `${dateTo}T23:59:59.999Z`)) return false;
      return true;
    });
    // Whatever needs the applicant's attention floats to the top; within a
    // group, the most recently touched application comes first.
    return [...filtered].sort((a, b) => {
      const aNeeds = APPLICANT_ACTION_STATUSES.includes(a.status) ? 0 : 1;
      const bNeeds = APPLICANT_ACTION_STATUSES.includes(b.status) ? 0 : 1;
      if (aNeeds !== bNeeds) return aNeeds - bNeeds;
      const aAt = a.submittedAt ?? a.createdAt;
      const bAt = b.submittedAt ?? b.createdAt;
      return aAt < bAt ? 1 : aAt > bAt ? -1 : 0;
    });
  }, [allItems, search, statusFilter, kindFilter, dateFrom, dateTo, bucketFilter]);

  const page = paginate(items);

  function clearFilters() {
    setSearch('');
    setStatusFilter(null);
    setKindFilter(null);
    setDateFrom('');
    setDateTo('');
    setBucketFilter(null);
    setPageIndex(0);
  }

  function toggleBucket(bucket: Exclude<Bucket, null>) {
    setBucketFilter((current) => (current === bucket ? null : bucket));
    setPageIndex(0);
    setTab('applications');
  }

  const statusLabel = (status: LicenseStatus) =>
    t(`applications.status.${status}`, { defaultValue: status });

  /**
   * The exam leg gets the stage the records actually add up to; everything
   * else keeps the plain status label.
   */
  const applicationStatusLabel = (app: LicenseApplication) => {
    const stage = examStageFor(
      app,
      registrationForApplication(app.id, examRegistrations),
    );
    if (!stage) return statusLabel(app.status);
    const registration = registrationForApplication(app.id, examRegistrations);
    return t(`applications.examStage.${stage}`, {
      defaultValue: statusLabel(app.status),
      date: registration?.exam?.date ? showDate(registration.exam.date) : '',
    });
  };

  const allStatuses = Object.keys(STATUS_COLORS) as LicenseStatus[];

  const columns = [
    ...applicationColumns(t, {
      language: i18n.language,
      showDate,
      statusLabel,
      applicationStatusLabel,
    }),
    applicationActionsColumn(t, {
      can,
      bypassEnabled: capabilities?.bypassEnabled ?? false,
      bypassing,
      isPaying,
      requestingExamFee,
      onBypass: (app) => handleBypass(app.id),
      onCertificate: (app) => openCertificateForApplication(app.id),
      onPay: (app) => pay(app.id),
      onRetakeExam: (app) => retakeExamFee(app.id),
      onRegisterForExam: () => navigate('/exams'),
      onOpen: (app) =>
        navigate(`/licensing/${app.licenseType?.key ?? 'FREIGHT_FORWARDER'}/applications/${app.id}`),
      onDiscard: (app) =>
        setDiscardTarget({ id: app.id, label: app.applicationNumber }),
    }),
  ];

  return (
    <Container size="lg" py="lg">
      <Stack gap="lg">
        <Group justify="space-between" align="flex-end" wrap="wrap">
          <Box>
            <Title order={2}>{t('applications.title')}</Title>
            <Text c="dimmed" size="sm" mt={2}>
              {t('applications.subtitle')}
            </Text>
          </Box>
          <RequirePermission anyOf={[LICENSE_PERMISSIONS.CREATE_APPLICATION]} hideOnly>
            <Button leftSection={<IconPlus size={16} />} onClick={() => changeTab('apply')}>
              {t('applications.newApplication')}
            </Button>
          </RequirePermission>
        </Group>

        <SimpleGrid cols={{ base: 2, md: 4 }} spacing="md">
          <StatTile
            label={t('applications.stats.needsYou')}
            value={counts.needsYou}
            icon={IconAlertTriangle}
            color="orange"
            active={bucketFilter === 'needsYou'}
            onClick={() => toggleBucket('needsYou')}
          />
          <StatTile
            label={t('applications.stats.inProgress')}
            value={counts.inProgress}
            icon={IconClockHour4}
            color="blue"
            active={bucketFilter === 'inProgress'}
            onClick={() => toggleBucket('inProgress')}
          />
          <StatTile
            label={t('applications.stats.completed')}
            value={counts.completed}
            icon={IconClipboardList}
            color="teal"
            active={bucketFilter === 'completed'}
            onClick={() => toggleBucket('completed')}
          />
          <StatTile
            label={t('applications.stats.activeLicences')}
            value={activeLicences.length}
            icon={IconCertificate}
            color="grape"
            active={tab === 'licences'}
            onClick={() => changeTab('licences')}
          />
        </SimpleGrid>

        <Tabs
          value={tab}
          onChange={(v) => changeTab((v as Tab) ?? 'applications')}
          variant="pills"
          classNames={{ list: classes.list, tab: classes.tab }}
        >
          <Tabs.List>
            <Tabs.Tab value="applications">{t('applications.tabs.applications')}</Tabs.Tab>
            <Tabs.Tab value="licences">{t('applications.tabs.licences')}</Tabs.Tab>
            <Tabs.Tab value="apply">{t('applications.tabs.apply')}</Tabs.Tab>
          </Tabs.List>
        </Tabs>

        {tab === 'applications' && (
          <Stack gap="md">
            {items.some((a) => a.status === 'PAYMENT_CONFIRMED' || a.status === 'PAID') && (
              <Alert variant="light" color="teal" radius="md">
                {t('applications.notice.paymentReceived')}
              </Alert>
            )}

            {allItems.length > 0 && (
              <Paper withBorder p="sm">
                <Group gap="sm" align="flex-end" wrap="wrap">
                  <TextInput
                    label={t('applications.filters.search')}
                    placeholder={t('applications.filters.searchPlaceholder')}
                    leftSection={<IconSearch size={14} />}
                    value={search}
                    onChange={(e) => {
                      setSearch(e.currentTarget.value);
                      setPageIndex(0);
                    }}
                    w={220}
                  />
                  <Select
                    label={t('applications.filters.status')}
                    placeholder={t('applications.filters.any')}
                    data={allStatuses.map((s) => ({ value: s, label: statusLabel(s) }))}
                    value={statusFilter}
                    onChange={(v) => {
                      setStatusFilter(v as LicenseStatus | null);
                      setPageIndex(0);
                    }}
                    clearable
                    w={200}
                  />
                  <Select
                    label={t('applications.filters.kind')}
                    placeholder={t('applications.filters.any')}
                    data={[
                      { value: 'NEW', label: t('applications.table.kindNew') },
                      { value: 'RENEWAL', label: t('applications.table.kindRenewal') },
                      { value: 'REISSUE', label: t('applications.table.kindReissue') },
                    ]}
                    value={kindFilter}
                    onChange={(v) => {
                      setKindFilter(v as ApplicationKind | null);
                      setPageIndex(0);
                    }}
                    clearable
                    w={160}
                  />
                  <AmharicDatePicker
                    label={t('applications.filters.from')}
                    value={dateFrom}
                    onChange={(v) => {
                      setDateFrom(v);
                      setPageIndex(0);
                    }}
                    dateFormat="date"
                    w={160}
                  />
                  <AmharicDatePicker
                    label={t('applications.filters.to')}
                    value={dateTo}
                    onChange={(v) => {
                      setDateTo(v);
                      setPageIndex(0);
                    }}
                    dateFormat="date"
                    w={160}
                  />
                  {hasFilters && (
                    <Button variant="subtle" leftSection={<IconX size={14} />} onClick={clearFilters}>
                      {t('applications.filters.clear')}
                    </Button>
                  )}
                </Group>
              </Paper>
            )}

            {!isFetching && items.length === 0 ? (
              <EmptyState
                icon={IconFileText}
                title={hasFilters ? t('applications.empty.noMatchTitle') : t('applications.empty.noneTitle')}
                description={hasFilters ? t('applications.empty.noMatchBody') : t('applications.empty.noneBody')}
                action={
                  hasFilters
                    ? { label: t('applications.empty.clearFilters'), onClick: clearFilters }
                    : { label: t('applications.empty.browse'), onClick: () => changeTab('apply') }
                }
              />
            ) : (
              <AdvancedTable
                columns={columns}
                data={page.rows}
                tableName={t('applications.tabs.applications')}
                itemCount={page.itemCount}
                pageIndex={page.pageIndex}
                onPageChange={setPageIndex}
                pageSize={pageSize}
                onPageSizeChange={setPageSize}
                refresh={refetch}
                isLoading={isFetching}
              />
            )}
          </Stack>
        )}

        {tab === 'licences' && (
          <Stack gap="md">
            {expiringSoon.length > 0 && (
              <Alert
                variant="light"
                color="orange"
                radius="md"
                icon={<IconClockHour4 size={18} />}
                title={t('applications.notice.expiringSoon', { count: expiringSoon.length })}
              >
                <Text size="sm">
                  {expiringSoon
                    .map((l) => `${l.certificateNumber} — ${l.daysUntilExpiry ?? daysUntil(l.expiryDate)}d`)
                    .join(' · ')}
                </Text>
              </Alert>
            )}
            {isFetchingLicences ? (
              <SimpleGrid cols={{ base: 1, sm: 2, lg: 3 }} spacing="md">
                <Skeleton height={160} radius="md" />
                <Skeleton height={160} radius="md" />
                <Skeleton height={160} radius="md" />
              </SimpleGrid>
            ) : licenceItems.length === 0 ? (
              <EmptyState icon={IconCertificate} title={t('applications.licences.empty')} />
            ) : (
              <SimpleGrid cols={{ base: 1, sm: 2, lg: 3 }} spacing="md">
                {licenceItems.map((license) => (
                  <LicenseCard
                    key={license.id}
                    license={license}
                    isDownloading={isDownloadingCert}
                    isRenewing={isRenewing}
                    isReissuing={isReissuing}
                    onDownload={() => downloadCertificate(license.id)}
                    onRenew={() => renewLicense(license)}
                    onReissue={() => reissueLicense(license)}
                  />
                ))}
              </SimpleGrid>
            )}
          </Stack>
        )}

        {tab === 'apply' && <LicenseCatalogue />}
      </Stack>

      <ConfirmModal
        opened={!!discardTarget}
        onClose={() => setDiscardTarget(null)}
        onConfirm={handleDiscard}
        loading={discarding}
        title={t('applications.actions.discard')}
        message={t('applications.actions.discardConfirm', {
          number: discardTarget?.label ?? '',
        })}
        confirmLabel={t('applications.actions.discard')}
        cancelLabel={t('common.cancel')}
      />
    </Container>
  );
}

// --------------------------------------------------------------- components

function StatTile({
  label,
  value,
  icon: Icon,
  color,
  active,
  onClick,
}: {
  label: string;
  value: number;
  icon: typeof IconAlertTriangle;
  color: string;
  active: boolean;
  onClick: () => void;
}) {
  return (
    <Card
      withBorder
      radius="md"
      padding="md"
      className={`${classes.tile} ${active ? classes.tileActive : ''}`}
      onClick={onClick}
    >
      <Group justify="space-between" align="flex-start" wrap="nowrap">
        <Box>
          <Text size="xs" c="dimmed" tt="uppercase" fw={600}>
            {label}
          </Text>
          <Text fz={30} fw={700} lh={1.2} mt={4}>
            {value}
          </Text>
        </Box>
        <ThemeIcon variant="light" color={color} radius="md" size="lg">
          <Icon size={18} />
        </ThemeIcon>
      </Group>
    </Card>
  );
}


export default MyApplicationsPage;
