import { useState } from 'react';
import {
  Badge,
  Button,
  Card,
  Group,
  Loader,
  Modal,
  Stack,
  Table,
  Text,
  Textarea,
  Title,
} from '@mantine/core';
import { IconClipboardList, IconFileText, IconGavel } from '@tabler/icons-react';
import { notify } from '@ema-platform/ui';
import { useDateDisplayer } from '@ema-platform/shared';
import {
  useApiQuery,
  useApiMutation,
  extractErrorMessage,
  openAuthedDocument,
} from '@ema-platform/api';

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

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

interface MyRegistration {
  id: string;
  admissionNumber: string;
  createdAt: string;
  kind: 'NEW' | 'RETAKE';
  attemptNumber: number;
  attendanceStatus: AttendanceStatus;
  exam?: OpenExam;
}

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

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

const ATTENDANCE_COLOR: Record<AttendanceStatus, string> = {
  REGISTERED: 'gray',
  PRESENT: 'teal',
  LATE: 'yellow',
  ABSENT: 'red',
  WITHDRAWN: 'orange',
  DISQUALIFIED: 'red',
};

/**
 * 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 [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 } = 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 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>
    );
  }

  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}>{exam.title?.en}</Text>
                  <Text size="xs" c="dimmed">
                    {exam.certification?.name?.en ?? ''} ·{' '}
                    {showDate(exam.date)}
                    {exam.venue ? ` · ${exam.venue}` : ''}
                  </Text>
                </div>
                {registeredExamIds.has(exam.id) ? (
                  <Badge color="teal" variant="light">
                    Registered
                  </Badge>
                ) : (
                  <Button
                    size="compact-sm"
                    loading={registering}
                    leftSection={<IconClipboardList size={14} />}
                    onClick={() => register(exam)}
                  >
                    Register
                  </Button>
                )}
              </Group>
            </Card>
          ))
        )}
      </Stack>

      <Stack gap="xs">
        <Title order={4}>My registrations</Title>
        {(mine ?? []).length === 0 ? (
          <Card withBorder radius="md" p="lg">
            <Text size="sm" c="dimmed" ta="center">
              No exam registrations yet.
            </Text>
          </Card>
        ) : (
          <Table.ScrollContainer minWidth={720}>
            <Table striped>
              <Table.Thead>
                <Table.Tr>
                  <Table.Th>Admission №</Table.Th>
                  <Table.Th>Examination</Table.Th>
                  <Table.Th>Date</Table.Th>
                  <Table.Th>Venue</Table.Th>
                  <Table.Th>Attempt</Table.Th>
                  <Table.Th>Attendance</Table.Th>
                  <Table.Th>Slip</Table.Th>
                </Table.Tr>
              </Table.Thead>
              <Table.Tbody>
                {(mine ?? []).map((registration) => (
                  <Table.Tr key={registration.id}>
                    <Table.Td>
                      <Text ff="monospace" size="sm" fw={600}>
                        {registration.admissionNumber}
                      </Text>
                    </Table.Td>
                    <Table.Td>{registration.exam?.title?.en ?? '—'}</Table.Td>
                    <Table.Td>{showDate(registration.exam?.date)}</Table.Td>
                    <Table.Td>{registration.exam?.venue ?? '—'}</Table.Td>
                    <Table.Td>
                      <Badge
                        size="sm"
                        variant="light"
                        color={registration.kind === 'RETAKE' ? 'orange' : 'blue'}
                      >
                        {registration.kind === 'RETAKE'
                          ? `Retake · ${registration.attemptNumber}`
                          : 'First sitting'}
                      </Badge>
                    </Table.Td>
                    <Table.Td>
                      <Badge
                        size="sm"
                        variant="light"
                        color={
                          ATTENDANCE_COLOR[registration.attendanceStatus] ??
                          'gray'
                        }
                      >
                        {registration.attendanceStatus}
                      </Badge>
                    </Table.Td>
                    <Table.Td>
                      <Button
                        size="compact-xs"
                        variant="light"
                        leftSection={<IconFileText size={13} />}
                        onClick={() => downloadSlip(registration)}
                      >
                        Slip
                      </Button>
                    </Table.Td>
                  </Table.Tr>
                ))}
              </Table.Tbody>
            </Table>
          </Table.ScrollContainer>
        )}
      </Stack>

      <Stack gap="xs">
        <Title order={4}>My results</Title>
        {(results ?? []).length === 0 ? (
          <Card withBorder radius="md" p="lg">
            <Text size="sm" c="dimmed" ta="center">
              No results have been published yet. Marks appear here once the
              authority approves and publishes them.
            </Text>
          </Card>
        ) : (
          <Table.ScrollContainer minWidth={720}>
            <Table striped>
              <Table.Thead>
                <Table.Tr>
                  <Table.Th>Examination</Table.Th>
                  <Table.Th>Published</Table.Th>
                  <Table.Th>Score</Table.Th>
                  <Table.Th>Outcome</Table.Th>
                  <Table.Th>Appeal</Table.Th>
                </Table.Tr>
              </Table.Thead>
              <Table.Tbody>
                {(results ?? []).map((result) => {
                  const appeal = (appeals ?? []).find(
                    (a) => a.resultId === result.id,
                  );
                  return (
                    <Table.Tr key={result.id}>
                      <Table.Td>{result.exam?.title?.en ?? '—'}</Table.Td>
                      <Table.Td>{showDate(result.publishedAt)}</Table.Td>
                      <Table.Td>
                        <Text fw={600} size="sm">
                          {result.totalScore}
                        </Text>
                      </Table.Td>
                      <Table.Td>
                        <Badge
                          variant="light"
                          color={result.status === 'PASSED' ? 'teal' : 'red'}
                        >
                          {result.status}
                        </Badge>
                      </Table.Td>
                      <Table.Td>
                        {appeal ? (
                          <Badge size="sm" variant="light" color="grape">
                            {appeal.appealNumber} · {appeal.status}
                          </Badge>
                        ) : (
                          <Button
                            size="compact-xs"
                            variant="light"
                            color="grape"
                            leftSection={<IconGavel size={13} />}
                            onClick={() => setAppealFor(result)}
                          >
                            Appeal
                          </Button>
                        )}
                      </Table.Td>
                    </Table.Tr>
                  );
                })}
              </Table.Tbody>
            </Table>
          </Table.ScrollContainer>
        )}
      </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 {appealFor?.exam?.title?.en ?? '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;
