import { useState } from 'react';
import { Link } from 'react-router-dom';
import { api } from '../api/client';
import { useAction, useQuery } from '../lib/hooks';
import { useToast } from '../lib/toast';
import { ago, duration, interval, num, until, when } from '../lib/format';
import { compareTasks, retimed, runTone, statusDot, taskStatus } from '../lib/tasks';
import {
  Banner,
  Button,
  Card,
  EmptyRow,
  Loading,
  Note,
  PageHead,
  TableWrap,
  Tag,
  Tiles,
} from '../components/ui';
import type { ScheduledTask, TasksResponse } from '../api/types';

/* Scheduled tasks: what the gateway does when nobody is watching.
 *
 * A directory, and — following the Users page it is now shaped like — very nearly only a
 * directory. It was a stack of cards holding a paragraph, a select and two switches per
 * task, which meant the one question this page is opened with, "is anything wrong", had to
 * be answered by reading every entry in turn. One row per task answers it by scanning a
 * column, and everything you can *do* to a task beyond starting it lives on the task's own
 * page.
 *
 * Run now is the exception that stays in the row. It is the only action here that is not a
 * change of configuration — it asks for something to happen once, and it is what an
 * operator comes to this page to press.
 *
 * Polled faster than the console's own heartbeat while a task is running, because having
 * pressed it the next thing they do is watch for the outcome, and a thirty-second poll
 * makes a two-second job look like one that did nothing. */

const IDLE_POLL_MS = 20_000;
const BUSY_POLL_MS = 3_000;

export function TasksPage() {
  const { wrap } = useToast();
  const { busy, run } = useAction();
  const [fast, setFast] = useState(false);
  const { data, error, loading, reload } = useQuery<TasksResponse>('/admin/api/tasks?limit=60', {
    pollMs: fast ? BUSY_POLL_MS : IDLE_POLL_MS,
  });

  const tasks = data?.tasks ?? [];
  const anyRunning = tasks.some((task) => task.running);
  if (anyRunning !== fast) setFast(anyRunning);

  const runNow = (task: ScheduledTask) =>
    run(task.id, async () => {
      await wrap(
        () => api.post(`/admin/api/tasks/${encodeURIComponent(task.id)}/run`),
        `${task.name} started.`,
      );
      await reload();
    });

  const failures = tasks.filter((task) => task.lastRun?.status === 'failed').length;
  const offSchedule = tasks.filter(retimed).length;
  const disabled = tasks.filter((task) => !task.enabled).length;

  const rows = [...tasks].sort(compareTasks);

  return (
    <>
      <PageHead
        title="Scheduled tasks"
        intro="The gateway's background work: what it does, when it last ran, how long it took and whether it worked. Every one of these can be started by hand."
      />

      <Banner message={error} />

      {loading ? (
        <Loading />
      ) : (
        <>
          <Tiles
            tiles={[
              { label: 'Tasks', value: num(tasks.length), icon: 'clock', tone: 'info' },
              {
                label: 'Running now',
                value: num(tasks.filter((task) => task.running).length),
                icon: 'pulse',
                tone: anyRunning ? 'ok' : undefined,
              },
              {
                label: 'Last run failed',
                value: num(failures),
                icon: 'alert',
                tone: failures > 0 ? 'bad' : undefined,
              },
              {
                label: 'Switched off',
                value: num(disabled),
                icon: 'power',
                tone: disabled > 0 ? 'warn' : undefined,
              },
              // Worth a tile of its own: a retimed task is the most likely explanation for
              // "why has this not run", and it is invisible on a page that only prints the
              // cadence currently in force.
              {
                label: 'Retimed',
                value: num(offSchedule),
                icon: 'clock',
                tone: offSchedule > 0 ? 'note' : undefined,
              },
            ]}
          />

          {failures > 0 ? (
            <Note tone="bad">
              A failed task publishes an administrative event, so the failure is in the activity feed and
              wherever your integrations send it — you did not have to be looking at this page.
            </Note>
          ) : null}

          <Card
            title="Tasks"
            intro="Anything wrong sorts to the top. Open a task to change its schedule, switch it off, or read its own run history."
            icon="clock"
            tone="info"
          >
            <TableWrap>
              <table>
                <thead>
                  <tr>
                    <th>Task</th>
                    <th>Service</th>
                    <th>Schedule</th>
                    <th>Last run</th>
                    <th className="num">Took</th>
                    <th>Next run</th>
                    <th>Status</th>
                    <th />
                  </tr>
                </thead>
                <tbody>
                  {rows.length === 0 ? (
                    <EmptyRow columns={8}>This gateway has no scheduled tasks registered.</EmptyRow>
                  ) : (
                    rows.map((task) => {
                      const status = taskStatus(task);
                      const last = task.lastRun;
                      const href = `/admin/tasks/${encodeURIComponent(task.id)}`;
                      return (
                        <tr key={task.id}>
                          {/* The name is the row's primary element and everything under it
                              is quieter, so a column of forty reads as a list of names
                              rather than as forty paragraphs. */}
                          <td>
                            <Link className="table-row-link" to={href}>
                              {task.name}
                            </Link>
                            <span className="table-sub">{task.description}</span>
                          </td>
                          <td className="muted nowrap">{task.group || 'Other'}</td>
                          {/* The declared cadence sits under an overridden one rather than
                              beside it: what happens next is the answer, and what the code
                              asked for is the footnote explaining why the row is retimed. */}
                          <td className="nowrap">
                            {interval(task.intervalSeconds)}
                            {retimed(task) ? (
                              <span className="table-sub">
                                default {interval(task.defaultIntervalSeconds)}
                              </span>
                            ) : null}
                          </td>
                          <td className="nowrap muted" title={last ? when(last.startedAt) : undefined}>
                            {last ? ago(last.startedAt) : 'never'}
                            {/* Only a failure earns a sub-line. A detail under every
                                successful row is a column of noise, and the detail is on
                                the task's own page either way. */}
                            {last?.error ? <span className="table-sub">{last.error}</span> : null}
                          </td>
                          <td className="num muted">{last ? duration(last.durationMs) : '—'}</td>
                          {/* A switched-off task still has a next run in the scheduler's
                              records and it is not going to happen, so the column says so
                              rather than printing a time that will pass with nothing at
                              the end of it. */}
                          <td className="nowrap muted">
                            {!task.enabled ? '—' : task.running ? 'now' : until(task.nextRun)}
                          </td>
                          <td className="nowrap">
                            <span className="row tight">
                              <span className="dot-state" data-tone={statusDot(status)} />
                              <Tag tone={status.tone}>
                                <span title={status.title}>{status.label}</span>
                              </Tag>
                            </span>
                          </td>
                          <td className="nowrap">
                            <span className="row tight">
                              <Button
                                size="sm"
                                icon="play"
                                busy={busy === task.id}
                                disabled={task.running}
                                title={`Run ${task.name} now`}
                                onClick={() => void runNow(task)}
                              />
                              <Link className="table-row-link" to={href}>
                                Details
                              </Link>
                            </span>
                          </td>
                        </tr>
                      );
                    })
                  )}
                </tbody>
              </table>
            </TableWrap>
          </Card>

          <Card
            title="Recent runs"
            intro="Every task together and in order, which is what shows two jobs interfering with each other."
            icon="history"
            tone="note"
          >
            <TableWrap>
              <table>
                <thead>
                  <tr>
                    <th className="nowrap">Started</th>
                    <th>Task</th>
                    <th>Trigger</th>
                    <th>Result</th>
                    <th className="num">Took</th>
                    <th>Detail</th>
                  </tr>
                </thead>
                <tbody>
                  {(data?.runs.length ?? 0) === 0 ? (
                    <EmptyRow columns={6}>No task has run yet.</EmptyRow>
                  ) : (
                    data?.runs.map((entry) => (
                      <tr key={entry.id}>
                        <td className="nowrap muted" title={when(entry.startedAt)}>
                          {ago(entry.startedAt)}
                        </td>
                        <td>
                          <Link
                            className="table-row-link"
                            to={`/admin/tasks/${encodeURIComponent(entry.taskId)}`}
                          >
                            {tasks.find((task) => task.id === entry.taskId)?.name ?? entry.taskId}
                          </Link>
                        </td>
                        <td className="muted">{entry.trigger}</td>
                        <td>
                          <Tag tone={runTone(entry.status)}>{entry.status}</Tag>
                        </td>
                        <td className="num muted">{duration(entry.durationMs)}</td>
                        <td className="muted">{entry.error || entry.detail || '—'}</td>
                      </tr>
                    ))
                  )}
                </tbody>
              </table>
            </TableWrap>
          </Card>
        </>
      )}
    </>
  );
}
