import { Link } from 'react-router-dom';
import { useQuery } from '../lib/hooks';
import { ago, initials, num, presence, recent, watchTime, when } from '../lib/format';
import {
  Banner,
  Card,
  EmptyRow,
  Loading,
  PageHead,
  TableWrap,
  Tag,
  Tiles,
} from '../components/ui';
import type { KnownClient } from '../api/types';

/* A directory, and only a directory. Everything you can *do* to a person lives on their own
   page: this list used to render a full seventeen-control settings editor for every account
   at once, which meant the page grew with the household and an operator scrolled past four
   other people's preferences to reach the one they came for. */

/* Watch time comes from Tracearr, and `matched` is the field that matters: a household
   running no Tracearr, and a person Tracearr has never seen, both arrive as zeroes. Drawing
   those as "0 min this week" would have an operator asking why somebody stopped watching
   when the real answer is that nothing was asked. */
interface WatchTime {
  matched: boolean;
  tracearrUsername?: string;
  weekMs: number;
  monthMs: number;
  totalMs: number;
  weekSessions: number;
  monthSessions: number;
  lastWatchedAt?: string;
}

interface Account {
  id: string;
  username: string;
  initials: string;
  shortName: string;
  lastSeen: string;
  devices: KnownClient[] | null;
  recommendations?: { prompted?: boolean; completed?: boolean };
  watchTime?: WatchTime;
}

interface AccountsResponse {
  accounts: Account[] | null;
}

/** seenAt is a timestamp as a number, with anything unreadable sorting last rather than
 *  first — an invalid date yields NaN, and NaN comparisons would scatter those rows. */
function seenAt(value: string | undefined): number {
  const at = value ? new Date(value).getTime() : 0;
  return Number.isFinite(at) ? at : 0;
}

/** A dash, and it says why on hover. Watch time comes from Tracearr; a household running
 *  none and a person it has never matched are both "not measured", never "none". */
function NotMeasured() {
  return (
    <span className="muted" title="No Tracearr sessions matched to this person">
      —
    </span>
  );
}

export function AccountsPage() {
  const { data, error, loading } = useQuery<AccountsResponse>('/admin/api/accounts', {
    pollMs: 60_000,
  });
  const accounts = data?.accounts ?? [];
  const devices = accounts.flatMap((account) => account.devices ?? []);
  const completed = accounts.filter((account) => account.recommendations?.completed).length;
  const queued = accounts.filter(
    (account) => account.recommendations?.prompted && !account.recommendations?.completed,
  ).length;
  /* Summed from the same rows the list draws, so the tile and the column underneath it can
     never disagree — a separate total query is how those two come apart. */
  const tracked = accounts.filter((account) => account.watchTime?.matched);
  const weekMs = tracked.reduce((total, account) => total + (account.watchTime?.weekMs ?? 0), 0);

  /* Most recently seen first, so the table means something without being sorted. Whoever
     is using Memby right now is the row an operator opening this page is looking for, and a
     person who has never signed in from a device sorts to the bottom rather than the top. */
  const rows = [...accounts].sort(
    (a, b) => seenAt(b.lastSeen) - seenAt(a.lastSeen),
  );

  return (
    <>
      <PageHead title="Users" intro="Who uses Memby, and which devices they are signed in to." />
      <Banner message={error} />

      {loading ? (
        <Loading />
      ) : (
        <>
          <Tiles
            tiles={[
              { label: 'Memby users', value: num(accounts.length), icon: 'people', tone: 'note' },
              { label: 'signed-in devices', value: num(devices.length), icon: 'tv', tone: 'info' },
              {
                label: 'active in the last 15 mins',
                value: num(devices.filter((device) => recent(device.lastSeen)).length),
                icon: 'pulse',
                tone: 'ok',
              },
              { label: 'recommendation setups completed', value: num(completed), icon: 'check', tone: 'ok' },
              { label: 'setup prompts queued', value: num(queued), icon: 'sparkle', tone: 'note' },
              ...(tracked.length
                ? [
                    {
                      label: 'watch time this week',
                      value: watchTime(weekMs),
                      icon: 'pulse' as const,
                      tone: 'data' as const,
                    },
                  ]
                : []),
            ]}
          />

          <Card title="Users" icon="people" tone="note">
            <TableWrap>
              <table>
                <thead>
                  <tr>
                    <th>User</th>
                    <th>Short name</th>
                    <th className="num">Devices</th>
                    <th className="num">This week</th>
                    <th className="num">This month</th>
                    <th>Recommendations</th>
                    <th>Last seen</th>
                  </tr>
                </thead>
                <tbody>
                  {rows.length === 0 ? (
                    <EmptyRow columns={7}>
                      No one has signed in to Memby yet. Emby-only accounts are intentionally not listed here.
                    </EmptyRow>
                  ) : (
                    rows.map((account) => {
                      const list = account.devices ?? [];
                      const active = list.filter((device) => recent(device.lastSeen)).length;
                      const state = account.recommendations?.completed
                        ? { label: 'personalised', tone: 'ok' as const }
                        : account.recommendations?.prompted
                          ? { label: 'prompt queued', tone: 'warn' as const }
                          : { label: 'not invited', tone: undefined };
                      const seen = presence(account.lastSeen);
                      const watched = account.watchTime;
                      return (
                        <tr key={account.id}>
                          <td>
                            <span className="row tight">
                              <span className="dot-state" data-tone={seen.tone} title={seen.label} />
                              <span className="avatar">{account.initials || initials(account.username)}</span>
                              <Link
                                className="table-row-link"
                                to={`/admin/accounts/${encodeURIComponent(account.id)}`}
                              >
                                {account.username || 'Unnamed user'}
                              </Link>
                            </span>
                          </td>
                          {/* Blank is the ordinary state and not a gap: the launcher greets
                              somebody by their account name unless an operator has given
                              Memby a friendlier one, and saying so beats a bare dash. */}
                          <td>
                            {account.shortName || (
                              <span className="muted" title="Memby greets them by their account name">
                                account name
                              </span>
                            )}
                          </td>
                          <td className="num">
                            {num(list.length)}
                            {/* Only where there is something to say. A sub-line under every
                                row reading "0 active now" is a column of noise. */}
                            {active ? <span className="table-sub">{num(active)} active now</span> : null}
                          </td>
                          {/* The month sits beside the week because a quiet week only means
                              something next to the month around it. Both are a dash rather
                              than a zero for somebody Tracearr has never seen: "0 min" would
                              have an operator investigating a person when the real answer is
                              that nothing was ever asked. */}
                          <td className="num">
                            {watched?.matched ? watchTime(watched.weekMs) : <NotMeasured />}
                          </td>
                          <td className="num muted">
                            {watched?.matched ? watchTime(watched.monthMs) : <NotMeasured />}
                          </td>
                          <td>
                            <Tag tone={state.tone}>{state.label}</Tag>
                          </td>
                          <td className="nowrap muted" title={when(account.lastSeen)}>
                            {ago(account.lastSeen)}
                          </td>
                        </tr>
                      );
                    })
                  )}
                </tbody>
              </table>
            </TableWrap>
          </Card>
        </>
      )}
    </>
  );
}

/** The account page reads the same list to find the person it is about. */
export type { Account, AccountsResponse };
