import { useState } from 'react';
import { Link, useParams } 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 { cadenceChoices, retimed, runTone, taskStatus } from '../lib/tasks';
import {
  Banner,
  Button,
  Card,
  Empty,
  EmptyRow,
  Field,
  Loading,
  Note,
  PageHead,
  TableWrap,
  Tag,
  Tiles,
  Toggle,
} from '../components/ui';
import type { ScheduledTask, TasksResponse } from '../api/types';

/* One scheduled task.
 *
 * The list is a directory and this is where a task is actually operated on: its cadence,
 * its switch, and its own run history rather than the whole gateway's. That split is the
 * Users/User one, and it is what let the list become a table an operator can scan — a
 * select and two switches per row is exactly the clutter that made forty tasks unreadable.
 *
 * It reads the same /admin/api/tasks the list does, with `task=` set, which is why there
 * is no second endpoint behind this page: the response already carries every task (so the
 * page can find the one it is about, and print it with the same fields the list used) and
 * the `task` parameter narrows only the run history. The limit is higher than the list's
 * because history is the whole reason somebody comes here. */

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

/** What the history says about the task, as opposed to what its last run says.
 *
 *  A task that fails one run in twenty and a task that has failed every run since Tuesday
 *  look identical from a status column, and the second is the one worth being told about.
 *  Averaged over the window the page holds rather than over all time, because that is the
 *  only thing it has — and it says how many runs it is averaging, so a figure drawn from
 *  three runs cannot be mistaken for a settled one. */
function summarise(runs: { status: string; durationMs: number }[]) {
  const finished = runs.filter((run) => run.status !== 'running');
  const failed = finished.filter((run) => run.status === 'failed').length;
  const timed = finished.filter((run) => run.durationMs > 0);
  const averageMs = timed.length
    ? timed.reduce((total, run) => total + run.durationMs, 0) / timed.length
    : 0;
  return { runs: finished.length, failed, averageMs, timed: timed.length };
}

export function TaskPage() {
  const { taskId = '' } = useParams();
  const { wrap } = useToast();
  const { busy, run } = useAction();
  const [fast, setFast] = useState(false);

  const { data, error, loading, reload } = useQuery<TasksResponse>(
    `/admin/api/tasks?task=${encodeURIComponent(taskId)}&limit=${HISTORY_LIMIT}`,
    { pollMs: fast ? BUSY_POLL_MS : IDLE_POLL_MS, enabled: Boolean(taskId) },
  );

  const task = data?.tasks.find((entry) => entry.id === taskId);
  const running = Boolean(task?.running);
  if (running !== fast) setFast(running);

  const runs = data?.runs ?? [];
  const status = task ? taskStatus(task) : undefined;
  const summary = summarise(runs);

  const act = (key: string, message: string, body: Record<string, unknown>) =>
    run(key, async () => {
      await wrap(() => api.put(`/admin/api/tasks/${encodeURIComponent(taskId)}`, body), message);
      await reload();
    });

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

  return (
    <>
      <PageHead
        title={task?.name || taskId}
        intro={task?.description}
        icon="clock"
        crumbs={
          <>
            <Link to="/admin/tasks">Scheduled tasks</Link>
            <span>/</span>
            <span>{task?.name || taskId}</span>
          </>
        }
        actions={
          task ? (
            <Button
              variant="primary"
              icon="play"
              busy={busy === 'run'}
              disabled={task.running}
              onClick={() => void runNow(task)}
            >
              {task.running ? 'Running' : 'Run now'}
            </Button>
          ) : undefined
        }
      />

      <Banner message={error} />

      {loading ? (
        <Loading />
      ) : !task ? (
        /* A task id that no longer exists is an ordinary thing to arrive at — a bookmark,
           or a job removed in a deployment — so it is stated rather than left as an empty
           page, and the way back is named. */
        <Card title="No such task" icon="alert" tone="bad">
          <Empty>
            This gateway has no task called <code>{taskId}</code>. It may have been renamed or removed.{' '}
            <Link to="/admin/tasks">Back to scheduled tasks</Link>.
          </Empty>
        </Card>
      ) : (
        <>
          <Tiles
            tiles={[
              {
                label: 'Status',
                value: status?.label ?? '—',
                small: true,
                icon: 'pulse',
                tone: status?.tone,
              },
              { label: 'Service', value: task.group || 'Other', small: true, icon: 'chip', tone: 'info' },
              {
                label: 'Runs every',
                value: interval(task.intervalSeconds),
                small: true,
                icon: 'clock',
                tone: retimed(task) ? 'note' : undefined,
              },
              {
                label: 'Next run',
                value: task.enabled ? (task.running ? 'now' : until(task.nextRun)) : 'not scheduled',
                small: true,
                icon: 'history',
                tone: task.enabled ? undefined : 'warn',
              },
              {
                label: `Failed of the last ${num(summary.runs)}`,
                value: num(summary.failed),
                icon: 'alert',
                tone: summary.failed > 0 ? 'bad' : undefined,
              },
              // Averaged only over runs that recorded a duration, and the label says so:
              // a mean that quietly counted skipped runs as instant would understate
              // every job whose ordinary answer is "nothing to do".
              {
                label: `Average of ${num(summary.timed)} timed runs`,
                value: summary.timed ? duration(Math.round(summary.averageMs)) : '—',
                small: true,
                icon: 'chart',
                tone: 'data',
              },
            ]}
          />

          {task.lastRun?.error ? (
            <Note tone="bad">
              The last run failed: {task.lastRun.error}
            </Note>
          ) : null}

          <Card
            title="Schedule"
            intro="How often the gateway runs this on its own, and whether it runs it at all."
            icon="sliders"
            tone="info"
          >
            <div className="row">
              {/* Disabled while a run is in flight: changing the cadence reschedules from
                  now, and doing that underneath a running job is how one run silently
                  becomes two. */}
              <Field label="How often it runs">
                <select
                  aria-label={`How often ${task.name} runs`}
                  value={task.intervalSeconds}
                  disabled={busy === 'interval' || task.running}
                  onChange={(event) =>
                    void act(
                      'interval',
                      `${task.name} now runs ${interval(Number(event.target.value))}.`,
                      { intervalSeconds: Number(event.target.value) },
                    )
                  }
                >
                  {cadenceChoices(task).map((choice) => (
                    <option key={choice.value} value={choice.value}>
                      {choice.label}
                      {choice.value === task.defaultIntervalSeconds ? ' (default)' : ''}
                    </option>
                  ))}
                </select>
              </Field>

              {/* Sending zero is how the API is told to forget an override, so this is a
                  separate call from the select rather than an option inside it — see
                  cadenceChoices. */}
              {retimed(task) ? (
                <Button
                  icon="refresh"
                  busy={busy === 'interval'}
                  disabled={task.running}
                  onClick={() =>
                    void act('interval', `${task.name} back to its default cadence.`, {
                      intervalSeconds: 0,
                    })
                  }
                >
                  Back to {interval(task.defaultIntervalSeconds)}
                </Button>
              ) : null}
            </div>

            <Toggle
              label="Run this on its schedule"
              hint="Switched off, the gateway leaves it alone. You can still start it by hand."
              checked={task.enabled}
              disabled={busy === 'enabled'}
              onChange={(next) =>
                void act(
                  'enabled',
                  next ? `${task.name} switched on.` : `${task.name} switched off.`,
                  { enabled: next },
                )
              }
            />

            {retimed(task) ? (
              <Note tone="note">
                This task is retimed: its code asks for {interval(task.defaultIntervalSeconds)} and it is
                set to {interval(task.intervalSeconds)}.
              </Note>
            ) : null}
          </Card>

          <Card
            title="Run history"
            intro="This task alone, newest first — which is what separates a job that fails occasionally from one that has stopped working."
            icon="history"
            tone="note"
          >
            <TableWrap>
              <table>
                <thead>
                  <tr>
                    <th className="nowrap">Started</th>
                    <th>Trigger</th>
                    <th>Result</th>
                    <th className="num">Took</th>
                    <th>Detail</th>
                  </tr>
                </thead>
                <tbody>
                  {runs.length === 0 ? (
                    <EmptyRow columns={5}>This task has not run yet.</EmptyRow>
                  ) : (
                    runs.map((entry) => (
                      <tr key={entry.id}>
                        <td className="nowrap muted" title={when(entry.startedAt)}>
                          {ago(entry.startedAt)}
                        </td>
                        <td className="muted">{entry.trigger}</td>
                        {/* The tag alone here, with no dot beside it. The status column on
                            the list is scanned across forty unrelated rows and earns the
                            second signal; this is one task's own history, where every row
                            is already about the same thing. */}
                        <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>
        </>
      )}
    </>
  );
}
