import { useMemo, useState } from 'react';
import { Link } from 'react-router-dom';
import { query } from '../api/client';
import { useQuery } from '../lib/hooks';
import { daysAgo, num, when } from '../lib/format';
import {
  Banner,
  Button,
  Card,
  Empty,
  EmptyRow,
  Field,
  Grid,
  Loading,
  PageHead,
  Segments,
  TableWrap,
  Tag,
  Tiles,
} from '../components/ui';
import { Bars } from '../components/ui';
import type { LoginDevicesResponse, LoginsResponse } from '../api/types';

/* The sign-in history.
 *
 * This is the page the whole login-analytics feature exists for, and its shape follows
 * from one observation: an operator arrives here with a *question*, not a browsing
 * intention — "did the bedroom TV connect this morning", "who is that address", "why does
 * this account keep failing" — so the filter bar is above the table and always visible,
 * not behind a disclosure, and every control in it maps to one server-side filter.
 *
 * Two tables of the same rows, deliberately, the stance the searches page takes: the
 * devices summary answers "which televisions are connecting", which is what you read when
 * you do not yet know where to look, and the log is uncollapsed and newest-first, which is
 * what you read when something has just happened. */

type View = 'log' | 'devices';

interface Filters {
  user: string;
  q: string;
  ip: string;
  outcome: '' | 'success' | 'failure';
  from: string;
  to: string;
}

const EMPTY: Filters = { user: '', q: '', ip: '', outcome: '', from: '', to: '' };

const WINDOWS = [
  { value: 1, label: 'Today' },
  { value: 7, label: '7 days' },
  { value: 30, label: '30 days' },
  { value: 0, label: 'All' },
] as const;

export function LoginsPage() {
  const [view, setView] = useState<View>('log');
  const [days, setDays] = useState<number>(7);
  const [filters, setFilters] = useState<Filters>(EMPTY);
  const [page, setPage] = useState(0);

  const limit = 100;

  // The window and the explicit date range are the same filter expressed two ways, and an
  // explicit `from` wins: an operator who typed a date meant it, and silently narrowing it
  // to the last seven days would answer a question they did not ask.
  const params = useMemo(
    () =>
      query({
        ...filters,
        days: filters.from ? undefined : days || undefined,
        limit,
        offset: page * limit,
      }),
    [filters, days, page],
  );

  const log = useQuery<LoginsResponse>(`/admin/api/logins${params}`, { enabled: view === 'log' });
  const devices = useQuery<LoginDevicesResponse>(`/admin/api/logins/devices${params}`, {
    enabled: view === 'devices',
  });

  const users = log.data?.users ?? devices.data?.users ?? [];
  const totals = log.data?.totals ?? devices.data?.totals;
  const retention = log.data?.retentionDays ?? devices.data?.retentionDays ?? 90;
  const loading = view === 'log' ? log.loading : devices.loading;
  const error = view === 'log' ? log.error : devices.error;

  const update = (patch: Partial<Filters>) => {
    setFilters((current) => ({ ...current, ...patch }));
    setPage(0);
  };

  const active =
    Object.entries(filters).some(([, value]) => value !== '') || Boolean(filters.from);

  return (
    <>
      <PageHead
        title="Sign-in history"
        intro="Every connection attempt, kept as history rather than as the latest state of a device. A television that has since been removed still appears here, because it still connected."
        actions={
          <Segments
            value={view}
            options={[
              { value: 'log', label: 'Log' },
              { value: 'devices', label: 'By device' },
            ]}
            onChange={setView}
          />
        }
      />

      <Banner message={error} />

      {totals ? (
        <Tiles
          tiles={[
            { label: 'Successful sign-ins', value: num(totals.logins), icon: 'key', tone: 'ok' },
            {
              label: 'Refused',
              value: num(totals.failures),
              icon: 'shield',
              tone: totals.failures > 0 ? 'warn' : undefined,
            },
            { label: 'Televisions', value: num(totals.devices), icon: 'tv', tone: 'info' },
            { label: 'People', value: num(totals.users), icon: 'people', tone: 'note' },
            { label: 'Addresses', value: num(totals.addresses), icon: 'globe', tone: 'data' },
            {
              label: 'History kept',
              value: `${retention} days`,
              small: true,
              icon: 'clock',
            },
          ]}
        />
      ) : null}

      {/* Fast filtering is most of what makes this table useful, so the controls sit above
          it rather than behind a "filters" disclosure nobody opens. */}
      <div className="filters">
        <Field label="Window">
          <Segments
            value={filters.from ? -1 : days}
            options={WINDOWS.map((entry) => ({ value: entry.value as number, label: entry.label }))}
            onChange={(next) => {
              setDays(next);
              update({ from: '', to: '' });
            }}
          />
        </Field>
        <Field label="Person">
          <select value={filters.user} onChange={(event) => update({ user: event.target.value })}>
            <option value="">Anyone</option>
            {users.map((user) => (
              <option key={user.id} value={user.id}>
                {user.username || user.id}
              </option>
            ))}
          </select>
        </Field>
        <Field label="Outcome">
          <select
            value={filters.outcome}
            onChange={(event) => update({ outcome: event.target.value as Filters['outcome'] })}
          >
            <option value="">Both</option>
            <option value="success">Got in</option>
            <option value="failure">Refused</option>
          </select>
        </Field>
        <Field label="Address">
          <input
            type="text"
            value={filters.ip}
            placeholder="10.0.0.4"
            onChange={(event) => update({ ip: event.target.value })}
          />
        </Field>
        <Field label="From">
          <input type="date" value={filters.from} onChange={(event) => update({ from: event.target.value })} />
        </Field>
        <Field label="To">
          <input type="date" value={filters.to} onChange={(event) => update({ to: event.target.value })} />
        </Field>
        <Field label="Search" grow>
          <input
            type="search"
            value={filters.q}
            placeholder="Name, device or address"
            onChange={(event) => update({ q: event.target.value })}
          />
        </Field>
        <div className="filter-actions">
          {active ? (
            <Button
              variant="quiet"
              size="sm"
              onClick={() => {
                setFilters(EMPTY);
                setPage(0);
              }}
            >
              Clear
            </Button>
          ) : null}
        </div>
      </div>

      {loading ? (
        <Loading />
      ) : view === 'log' ? (
        <LogView data={log.data} page={page} limit={limit} onPage={setPage} />
      ) : (
        <DeviceView data={devices.data} />
      )}
    </>
  );
}

function LogView({
  data,
  page,
  limit,
  onPage,
}: {
  data: LoginsResponse | undefined;
  page: number;
  limit: number;
  onPage: (next: number) => void;
}) {
  if (!data) return null;
  const shown = data.events.length;
  const from = data.total === 0 ? 0 : page * limit + 1;

  return (
    <>
      <Grid cols="wide">
        <Card
          title="Attempts per day"
          intro="Grouped in the server's own timezone, so an evening sign-in stays on the day it happened."
          icon="chart"
          tone="info"
        >
          <Bars
            data={data.days}
            labelOf={(row: (typeof data.days)[number]) => row.day}
            valueOf={(row: (typeof data.days)[number]) => row.logins + row.failures}
            toneOf={(row: (typeof data.days)[number]) => (row.failures > row.logins ? 'bad' : undefined)}
            title={(row: (typeof data.days)[number]) =>
              `${row.day}: ${row.logins} in, ${row.failures} refused, ${row.devices} televisions`
            }
          />
        </Card>

        <Card title="Where from" icon="globe" tone="data">
          {data.addresses.length === 0 ? (
            <Empty>No addresses in this window.</Empty>
          ) : (
            <div className="list">
              {data.addresses.slice(0, 8).map((address) => (
                <div className="list-item" key={address.ipAddress}>
                  <div className="list-body">
                    <b className="mono">{address.ipAddress}</b>
                    <p>
                      {num(address.logins)} in
                      {address.failures > 0 ? ` · ${num(address.failures)} refused` : ''}
                    </p>
                  </div>
                  {address.failures > 0 && address.logins === 0 ? <Tag tone="bad">only refused</Tag> : null}
                </div>
              ))}
            </div>
          )}
        </Card>
      </Grid>

      <Card
        title="Attempts"
        intro="Uncollapsed and newest first: this is what to read when somebody says a television will not sign in."
        icon="key"
        tone="ok"
        actions={
          <span className="filter-summary">
            {data.total === 0
              ? 'nothing matches'
              : `${num(from)}–${num(from + shown - 1)} of ${num(data.total)}`}
          </span>
        }
        footer={
          data.total > limit ? (
            <>
              <Button size="sm" disabled={page === 0} onClick={() => onPage(page - 1)}>
                Newer
              </Button>
              <Button
                size="sm"
                disabled={(page + 1) * limit >= data.total}
                onClick={() => onPage(page + 1)}
              >
                Older
              </Button>
            </>
          ) : undefined
        }
      >
        <TableWrap>
          <table>
            <thead>
              <tr>
                <th className="nowrap">When</th>
                <th>Person</th>
                <th>Television</th>
                <th className="nowrap">Address</th>
                <th>Build</th>
                <th>Outcome</th>
              </tr>
            </thead>
            <tbody>
              {data.events.length === 0 ? (
                <EmptyRow columns={6}>No sign-in attempts match these filters.</EmptyRow>
              ) : (
                data.events.map((event) => (
                  <tr key={event.id}>
                    <td className="nowrap muted">{when(event.occurredAt)}</td>
                    <td>{event.username || <span className="quiet">unknown</span>}</td>
                    <td>
                      {event.deviceId ? (
                        <Link className="table-row-link" to={`/admin/devices/${encodeURIComponent(event.deviceId)}`}>
                          {event.deviceName || event.deviceId}
                        </Link>
                      ) : (
                        <span className="quiet">—</span>
                      )}
                    </td>
                    <td className="mono nowrap">{event.ipAddress || '—'}</td>
                    <td className="mono">{event.clientVersion || '—'}</td>
                    <td className="nowrap">
                      {event.success ? (
                        event.newDevice ? (
                          <Tag tone="info">first sign-in</Tag>
                        ) : (
                          <Tag tone="ok">got in</Tag>
                        )
                      ) : (
                        <Tag tone="bad">{event.failureReason || 'refused'}</Tag>
                      )}
                    </td>
                  </tr>
                ))
              )}
            </tbody>
          </table>
        </TableWrap>
      </Card>
    </>
  );
}

function DeviceView({ data }: { data: LoginDevicesResponse | undefined }) {
  if (!data) return null;
  return (
    <Card
      title="Televisions"
      intro="Grouped from the history, not from the session list — a set whose session has expired still connected, and this is the record of it."
      icon="tv"
      tone="info"
    >
      <TableWrap>
        <table>
          <thead>
            <tr>
              <th>Television</th>
              <th>Person</th>
              <th className="num">Today</th>
              <th className="num">Sign-ins</th>
              <th className="num">Refused</th>
              <th className="num">Addresses</th>
              <th className="nowrap">Last address</th>
              <th className="nowrap">Last sign-in</th>
              <th>Build</th>
            </tr>
          </thead>
          <tbody>
            {data.devices.length === 0 ? (
              <EmptyRow columns={9}>No television has connected in this window.</EmptyRow>
            ) : (
              data.devices.map((device) => (
                <tr key={device.deviceId}>
                  <td>
                    <Link className="table-row-link" to={`/admin/devices/${encodeURIComponent(device.deviceId)}`}>
                      {device.deviceName || device.deviceId}
                    </Link>
                  </td>
                  <td className="muted">{device.username || '—'}</td>
                  <td className="num">{device.loginsToday > 0 ? num(device.loginsToday) : '—'}</td>
                  <td className="num">{num(device.logins)}</td>
                  <td className="num">
                    {device.failures > 0 ? <span className="mono">{num(device.failures)}</span> : '—'}
                  </td>
                  <td className="num">{num(device.distinctIps)}</td>
                  <td className="mono nowrap">{device.lastIp || '—'}</td>
                  {/* A device with failures and no successes has no last sign-in, which is
                      a real answer rather than a zero one. */}
                  <td className="nowrap muted">{device.lastLogin ? when(device.lastLogin) : '—'}</td>
                  <td className="mono">{device.clientVersion || '—'}</td>
                </tr>
              ))
            )}
          </tbody>
        </table>
      </TableWrap>
    </Card>
  );
}

/** Exported for the device page, which offers the same seven-day default. */
export const defaultWindowFrom = () => daysAgo(7);
