import { useState } from 'react';
import {
  Badge,
  Button,
  Card,
  Group,
  Loader,
  Modal,
  Stack,
  Text,
  Textarea,
  Title,
} from '@mantine/core';
import { IconClipboardList } from '@tabler/icons-react';
import { AdvancedTable, 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;
  exam?: OpenExam;
}

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 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(
        `Registered — admission number ${result.admissionNumber ?? 'issued'}`,
      );
      refetch();
    } catch (error) {
      const key = extractErrorMessage(error, 'Could not register');
      notify.error(
        key === 'seafarer_registration_required'
          ? 'An active seafarer registration is required to sit examinations.'
          : key === 'already_registered_for_exam'
            ? 'You are already registered for this session.'
            : key === 'subject_already_passed'
              ? 'You have already passed this subject — a resit is not needed.'
              : key,
      );
    }
  };

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

  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(`Appeal ${appeal.appealNumber ?? ''} submitted`);
      setAppealFor(null);
      setAppealReason('');
      refetchAppeals();
    } catch (error) {
      const key = extractErrorMessage(error, 'Could not submit the appeal');
      notify.error(
        key.startsWith('appeal_window_closed')
          ? `The appeal window (${key.split(':')[1] ?? ''} days from publication) has closed.`
          : key === 'appeal_already_open'
            ? 'An appeal on this result is already being considered.'
            : key,
      );
    }
  };

  if (loadingOpen || loadingMine || loadingResults) {
    return (
      <Group justify="center" py="xl">
        <Loader />
      </Group>
    );
  }

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

  return (
    <Stack maw={900} mx="auto">
      <Title order={2}>Examinations</Title>

      <Stack gap="xs">
        <Title order={4}>Open sessions</Title>
        {(open ?? []).length === 0 ? (
          <Card withBorder radius="md" p="lg">
            <Text size="sm" c="dimmed" ta="center">
              No upcoming sessions are open for registration.
            </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">
                    Registered
                  </Badge>
                ) : (
                  <RequirePermission anyOf={[PORTAL_PERMISSIONS.APPLY_EXAM]} hideOnly>
                    <Button
                      size="compact-sm"
                      loading={registering}
                      leftSection={<IconClipboardList size={14} />}
                      onClick={() => register(exam)}
                    >
                      Register
                    </Button>
                  </RequirePermission>
                )}
              </Group>
            </Card>
          ))
        )}
      </Stack>

      <Stack gap="xs">
        <Title order={4}>My registrations</Title>
        <AdvancedTable<MyRegistration>
          tableName="My registrations"
          columns={registrationColumns({
            can,
            localized,
            showDate,
            onDownloadSlip: downloadSlip,
          })}
          data={pagedRegistrations.rows}
          itemCount={pagedRegistrations.itemCount}
          pageIndex={pagedRegistrations.pageIndex}
          onPageChange={registrationTable.setPageIndex}
          pageSize={registrationTable.pageSize}
          refresh={refetch}
          emptyText="No exam registrations yet."
        />
      </Stack>

      <Stack gap="xs">
        <Title order={4}>My results</Title>
        <AdvancedTable<MyResult>
          tableName="My results"
          columns={resultColumns({
            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="No results have been published yet. Marks appear here once the authority approves and publishes them."
        />
      </Stack>

      <Modal
        opened={Boolean(appealFor)}
        onClose={() => setAppealFor(null)}
        title="Request a review of this result"
        radius="lg"
      >
        <Stack>
          <Text size="sm" c="dimmed">
            Explain what you believe went wrong with the marking or the
            administration of {localized(appealFor?.exam?.title) || 'this examination'}.
            Appeals must be lodged within 14 days of publication.
          </Text>
          <Textarea
            minRows={4}
            autosize
            label="Grounds for appeal"
            value={appealReason}
            onChange={(event) => setAppealReason(event.currentTarget.value)}
            required
          />
          <Group justify="flex-end">
            <Button variant="default" onClick={() => setAppealFor(null)}>
              Cancel
            </Button>
            <Button
              loading={appealing}
              disabled={appealReason.trim().length === 0}
              onClick={submitAppeal}
            >
              Submit appeal
            </Button>
          </Group>
        </Stack>
      </Modal>
    </Stack>
  );
}

export default ExamsPage;
