import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import {
  Badge,
  Button,
  Card,
  Group,
  Modal,
  Stack,
  Text,
  Textarea,
  Title,
} from '@mantine/core';
import { IconClipboardList } from '@tabler/icons-react';
import { useTranslation } from 'react-i18next';
import { AdvancedTable, PageLoader, notify, useServerTable } from '@ema-platform/ui';
import { useDateDisplayer } from '@ema-platform/shared';
import {
  useApiQuery,
  useApiMutation,
  extractErrorMessage,
  openAuthedDocument,
  useLocalized,
} from '@ema-platform/api';
import {
  PORTAL_PERMISSIONS,
  RequirePermission,
  usePermissions,
} from '@ema-platform/auth';
import { registrationColumns, resultColumns } from './columns';

export interface OpenExam {
  id: string;
  title: { en?: string; am?: string };
  date: string;
  venue: string | null;
  status: string;
  certification?: { name?: { en?: string } };
}

export type AttendanceStatus =
  | 'REGISTERED'
  | 'PRESENT'
  | 'ABSENT'
  | 'LATE'
  | 'WITHDRAWN'
  | 'DISQUALIFIED';

export interface MyRegistration {
  id: string;
  admissionNumber: string;
  createdAt: string;
  kind: 'NEW' | 'RETAKE';
  attemptNumber: number;
  attendanceStatus: AttendanceStatus;
  /** The certificate application this sitting answers, when there is one. */
  applicationId: string | null;
  exam?: OpenExam;
  /** The candidate's online sitting, when one has been started. */
  attempt?: { status: 'IN_PROGRESS' | 'SUBMITTED' | 'EXPIRED' } | null;
}

export interface MyResult {
  id: string;
  totalScore: number;
  status: 'PASSED' | 'FAILED';
  publishedAt: string | null;
  remark?: { en?: string } | null;
  exam?: OpenExam;
}

export interface MyAppeal {
  id: string;
  appealNumber: string;
  status: 'SUBMITTED' | 'UNDER_REVIEW' | 'UPHELD' | 'REJECTED';
  reason: string;
  decisionRemark: string | null;
  resultId: string;
}

/**
 * The candidate's examination home (US-EXAM-007/008/014/015/016): sessions to
 * sit, the admission slip to print, published results, and the appeal route
 * when a mark looks wrong.
 */
export function ExamsPage() {
  const { t } = useTranslation();
  const navigate = useNavigate();
  const showDate = useDateDisplayer();
  const localized = useLocalized();
  const [appealFor, setAppealFor] = useState<MyResult | null>(null);
  const [appealReason, setAppealReason] = useState('');

  const { data: open, isLoading: loadingOpen } = useApiQuery<OpenExam[]>({
    url: '/exams/open',
    method: 'GET',
  });
  const {
    data: mine,
    isLoading: loadingMine,
    refetch,
  } = useApiQuery<MyRegistration[]>({
    url: '/exams/registrations/mine',
    method: 'GET',
  });
  const {
    data: results,
    isLoading: loadingResults,
    refetch: refetchResults,
  } = useApiQuery<MyResult[]>({
    url: '/results/mine',
    method: 'GET',
  });
  const {
    data: appeals,
    refetch: refetchAppeals,
  } = useApiQuery<MyAppeal[]>({ url: '/results/appeals/mine', method: 'GET' });
  const [registerTrigger, { isLoading: registering }] = useApiMutation();
  const [appealTrigger, { isLoading: appealing }] = useApiMutation();
  const registrationTable = useServerTable();
  const resultTable = useServerTable();
  const { can } = usePermissions();

  const registeredExamIds = new Set((mine ?? []).map((r) => r.exam?.id));

  const register = async (exam: OpenExam) => {
    try {
      const result = (await registerTrigger({
        url: `/exams/${exam.id}/register`,
        method: 'POST',
      }).unwrap()) as { admissionNumber?: string };
      notify.success(
        t('exams.notify.registered', {
          admissionNumber:
            result.admissionNumber ?? t('exams.notify.admissionNumberPending'),
        }),
      );
      refetch();
    } catch (error) {
      const key = extractErrorMessage(error, t('exams.notify.registerFailed'));
      notify.error(
        key === 'seafarer_registration_required'
          ? t('exams.notify.seafarerRequired')
          : key === 'already_registered_for_exam'
            ? t('exams.notify.alreadyRegistered')
            : key === 'subject_already_passed'
              ? t('exams.notify.alreadyPassed')
              : key,
      );
    }
  };

  const downloadSlip = async (registration: MyRegistration) => {
    try {
      await openAuthedDocument(
        `/exams/registrations/${registration.id}/slip`,
        `admission-${registration.admissionNumber}.pdf`,
      );
    } catch (error) {
      notify.error(
        extractErrorMessage(error, t('exams.notify.slipFailed')),
      );
    }
  };

  const submitAppeal = async () => {
    if (!appealFor) return;
    try {
      const appeal = (await appealTrigger({
        url: `/results/${appealFor.id}/appeal`,
        method: 'POST',
        body: { reason: appealReason.trim() },
      }).unwrap()) as { appealNumber?: string };
      notify.success(
        t('exams.notify.appealSubmitted', { appealNumber: appeal.appealNumber ?? '' }),
      );
      setAppealFor(null);
      setAppealReason('');
      refetchAppeals();
    } catch (error) {
      const key = extractErrorMessage(error, t('exams.notify.appealFailed'));
      notify.error(
        key.startsWith('appeal_window_closed')
          ? t('exams.notify.appealWindowClosed', { days: key.split(':')[1] ?? '' })
          : key === 'appeal_already_open'
            ? t('exams.notify.appealAlreadyOpen')
            : key,
      );
    }
  };

  if (loadingOpen || loadingMine || loadingResults) {
    return <PageLoader label={t('exams.loading')} height={400} />;
  }

  const pagedRegistrations = registrationTable.paginate(mine ?? []);
  const pagedResults = resultTable.paginate(results ?? []);

  return (
    <Stack maw={900} mx="auto">
      <Title order={2}>{t('exams.title')}</Title>

      <Stack gap="xs">
        <Title order={4}>{t('exams.openSessions')}</Title>
        {(open ?? []).length === 0 ? (
          <Card withBorder radius="md" p="lg">
            <Text size="sm" c="dimmed" ta="center">
              {t('exams.noOpenSessions')}
            </Text>
          </Card>
        ) : (
          (open ?? []).map((exam) => (
            <Card key={exam.id} withBorder radius="md" p="md">
              <Group justify="space-between">
                <div>
                  <Text fw={600}>{localized(exam.title)}</Text>
                  <Text size="xs" c="dimmed">
                    {localized(exam.certification?.name)} ·{' '}
                    {showDate(exam.date)}
                    {exam.venue ? ` · ${exam.venue}` : ''}
                  </Text>
                </div>
                {registeredExamIds.has(exam.id) ? (
                  <Badge color="teal" variant="light">
                    {t('exams.registered')}
                  </Badge>
                ) : (
                  <RequirePermission anyOf={[PORTAL_PERMISSIONS.APPLY_EXAM]} hideOnly>
                    <Button
                      size="compact-sm"
                      loading={registering}
                      leftSection={<IconClipboardList size={14} />}
                      onClick={() => register(exam)}
                    >
                      {t('exams.register')}
                    </Button>
                  </RequirePermission>
                )}
              </Group>
            </Card>
          ))
        )}
      </Stack>

      <Stack gap="xs">
        <Title order={4}>{t('exams.myRegistrations')}</Title>
        <AdvancedTable<MyRegistration>
          tableName={t('exams.myRegistrations')}
          columns={registrationColumns(t, {
            can,
            localized,
            showDate,
            onDownloadSlip: downloadSlip,
            onStartExam: (registration) => navigate(`/exams/${registration.exam?.id}/take`),
          })}
          data={pagedRegistrations.rows}
          itemCount={pagedRegistrations.itemCount}
          pageIndex={pagedRegistrations.pageIndex}
          onPageChange={registrationTable.setPageIndex}
          pageSize={registrationTable.pageSize}
          refresh={refetch}
          emptyText={t('exams.noRegistrations')}
        />
      </Stack>

      <Stack gap="xs">
        <Title order={4}>{t('exams.myResults')}</Title>
        <AdvancedTable<MyResult>
          tableName={t('exams.myResults')}
          columns={resultColumns(t, {
            can,
            localized,
            showDate,
            appeals: appeals ?? [],
            onAppeal: setAppealFor,
          })}
          data={pagedResults.rows}
          itemCount={pagedResults.itemCount}
          pageIndex={pagedResults.pageIndex}
          onPageChange={resultTable.setPageIndex}
          pageSize={resultTable.pageSize}
          refresh={refetchResults}
          emptyText={t('exams.noResults')}
        />
      </Stack>

      <Modal
        opened={Boolean(appealFor)}
        onClose={() => setAppealFor(null)}
        title={t('exams.appealModal.title')}
        radius="lg"
      >
        <Stack>
          <Text size="sm" c="dimmed">
            {t('exams.appealModal.body', {
              examTitle:
                localized(appealFor?.exam?.title) ||
                t('exams.appealModal.defaultExamTitle'),
            })}
          </Text>
          <Textarea
            minRows={4}
            autosize
            label={t('exams.appealModal.reasonLabel')}
            value={appealReason}
            onChange={(event) => setAppealReason(event.currentTarget.value)}
            required
          />
          <Group justify="flex-end">
            <Button variant="default" onClick={() => setAppealFor(null)}>
              {t('common.cancel')}
            </Button>
            <Button
              loading={appealing}
              disabled={appealReason.trim().length === 0}
              onClick={submitAppeal}
            >
              {t('exams.appealModal.submit')}
            </Button>
          </Group>
        </Stack>
      </Modal>
    </Stack>
  );
}

export default ExamsPage;
