import { useCallback, useEffect, useRef, useState } from 'react';
import {
  ChevronDown,
  ChevronLeft,
  ChevronRight,
  ChevronUp,
  Info,
  RefreshCw,
  Search,
  TriangleAlert
} from 'lucide-react';
import { Badge, Button, Card, IconButton, Input, Select, Spinner } from '../components';
import { getLogs, messageFromLogsError, type LogEntry, type LogsSortDirection, type LogsSortField } from '../api';

const PAGE_SIZE_OPTIONS = ['25', '50', '100'];
const DEFAULT_PAGE_SIZE = 50;

// Client-local UI preference (not a Blazor-style server ConfigElement) — see docs/decisions.md
// 2026-07-11 "Logs page-size is a client-local preference".
const LOGS_PAGE_SIZE_STORAGE_KEY = 'ctv-logs-page-size';

const LEVEL_TONE: Record<string, 'neutral' | 'accent' | 'ok' | 'warn' | 'error'> = {
  Debug: 'neutral',
  Error: 'error',
  Fatal: 'error',
  Information: 'accent',
  Verbose: 'neutral',
  Warning: 'warn'
};

type LogsState =
  | { entries: LogEntry[]; error: null; status: 'success'; totalCount: number }
  | { entries: []; error: string; status: 'error'; totalCount: 0 }
  | { entries: []; error: null; status: 'loading'; totalCount: 0 };

function formatTimestamp(value: string): string {
  const date = new Date(value);
  return Number.isNaN(date.getTime()) ? value : date.toLocaleString();
}

function getStorage(): Storage | undefined {
  if (typeof window === 'undefined') {
    return undefined;
  }

  try {
    return window.localStorage;
  } catch {
    return undefined;
  }
}

function getStoredPageSize(): number {
  const stored = getStorage()?.getItem(LOGS_PAGE_SIZE_STORAGE_KEY) ?? null;
  return stored != null && PAGE_SIZE_OPTIONS.includes(stored) ? Number(stored) : DEFAULT_PAGE_SIZE;
}

function storePageSize(pageSize: number): void {
  getStorage()?.setItem(LOGS_PAGE_SIZE_STORAGE_KEY, String(pageSize));
}

interface SortableColumn {
  field: LogsSortField;
  label: string;
}

const COLUMNS: SortableColumn[] = [
  { field: 'timestamp', label: 'Timestamp' },
  { field: 'level', label: 'Level' }
];

export function LogsScreen() {
  const [filterInput, setFilterInput] = useState('');
  const [filter, setFilter] = useState('');
  const [pageNum, setPageNum] = useState(0);
  const [pageSize, setPageSize] = useState(() => getStoredPageSize());
  const [sortField, setSortField] = useState<LogsSortField>('timestamp');
  const [sortDirection, setSortDirection] = useState<LogsSortDirection>('desc');
  const [state, setState] = useState<LogsState>({ entries: [], error: null, status: 'loading', totalCount: 0 });
  const activeRef = useRef(true);
  const seqRef = useRef(0);

  useEffect(() => {
    activeRef.current = true;
    return () => {
      activeRef.current = false;
    };
  }, []);

  // Debounce free-text filtering: the filter is applied server-side (GetRecentLogEntries scans
  // the newest log file per request), so we wait for the user to pause typing before refetching.
  useEffect(() => {
    const handle = window.setTimeout(() => {
      setPageNum(0);
      setFilter(filterInput.trim());
    }, 300);

    return () => window.clearTimeout(handle);
  }, [filterInput]);

  // Fetch only; state updates happen in the async callbacks (never synchronously in the
  // effect body) so a stale table stays visible during refetches instead of flashing to
  // a loading spinner on every filter keystroke or page change.
  const load = useCallback(() => {
    const id = ++seqRef.current;
    getLogs({ filter, pageNum, pageSize, sortDirection, sortField })
      .then((paged) => {
        if (activeRef.current && id === seqRef.current) {
          setState({ entries: paged.page ?? [], error: null, status: 'success', totalCount: paged.totalCount ?? 0 });
        }
      })
      .catch((error: unknown) => {
        if (activeRef.current && id === seqRef.current) {
          setState({ entries: [], error: messageFromLogsError(error), status: 'error', totalCount: 0 });
        }
      });
  }, [filter, pageNum, pageSize, sortDirection, sortField]);

  useEffect(() => {
    load();
  }, [load]);

  const refresh = () => {
    setState({ entries: [], error: null, status: 'loading', totalCount: 0 });
    load();
  };

  const changePageSize = (nextPageSize: number) => {
    setPageNum(0);
    setPageSize(nextPageSize);
    storePageSize(nextPageSize);
  };

  const toggleSort = (field: LogsSortField) => {
    setPageNum(0);
    if (field === sortField) {
      setSortDirection((current) => (current === 'asc' ? 'desc' : 'asc'));
    } else {
      setSortField(field);
      setSortDirection('asc');
    }
  };

  const totalPages = state.status === 'success' ? Math.max(1, Math.ceil(state.totalCount / pageSize)) : 1;

  return (
    <div className="ctv-collections">
      <div className="ctv-channels-actionbar">
        <Input
          leadingIcon={<Search aria-hidden="true" size={14} />}
          onChange={(event) => setFilterInput(event.target.value)}
          placeholder="Filter by level or message…"
          value={filterInput}
        />
        <span className="ctv-channels-spacer" />
        <Select
          onChange={(event) => changePageSize(Number(event.target.value))}
          options={PAGE_SIZE_OPTIONS}
          style={{ width: 96 }}
          value={String(pageSize)}
        />
        <Button
          onClick={refresh}
          size="sm"
          startIcon={<RefreshCw aria-hidden="true" size={14} />}
          variant="secondary"
        >
          Refresh
        </Button>
      </div>

      {state.status === 'error' && (
        <div className="ctv-channels-error" role="alert">
          <TriangleAlert aria-hidden="true" size={15} />
          <span>{state.error}</span>
          <span className="ctv-channels-spacer" />
          <Button onClick={refresh} size="sm" variant="secondary">
            Retry
          </Button>
        </div>
      )}

      <Card padded={false}>
        {state.status === 'loading' ? (
          <div className="ctv-collections-loading" role="status">
            <Spinner size={18} />
            <span>Loading logs…</span>
          </div>
        ) : state.entries.length === 0 ? (
          <div className="ctv-collections-empty">
            {filter ? 'No log entries match this filter.' : 'No log entries found.'}
          </div>
        ) : (
          <div className="ctv-channels-table-frame">
            <div className="ctv-channels-table-scroll">
              <table aria-label="Recent log entries" className="ctv-channels-table">
                <thead>
                  <tr>
                    {COLUMNS.map((column) => {
                      const active = column.field === sortField;
                      return (
                        <th aria-sort={active ? (sortDirection === 'asc' ? 'ascending' : 'descending') : 'none'} key={column.field}>
                          <button
                            className="ctv-logs-sort-button"
                            onClick={() => toggleSort(column.field)}
                            type="button"
                          >
                            {column.label}
                            {active &&
                              (sortDirection === 'asc' ? (
                                <ChevronUp aria-hidden="true" size={13} />
                              ) : (
                                <ChevronDown aria-hidden="true" size={13} />
                              ))}
                          </button>
                        </th>
                      );
                    })}
                    <th>Message</th>
                  </tr>
                </thead>
                <tbody>
                  {state.entries.map((entry, index) => (
                    <tr key={`${entry.timestamp}-${index}`}>
                      <td style={{ fontFamily: 'var(--font-mono)', whiteSpace: 'nowrap' }}>
                        {formatTimestamp(entry.timestamp)}
                      </td>
                      <td>
                        <Badge tone={LEVEL_TONE[entry.level] ?? 'neutral'}>{entry.level}</Badge>
                      </td>
                      <td style={{ fontFamily: 'var(--font-mono)' }}>{entry.message}</td>
                    </tr>
                  ))}
                </tbody>
              </table>
            </div>
            <div className="ctv-channels-footer">
              <span>
                {state.totalCount} entr{state.totalCount === 1 ? 'y' : 'ies'}
                {filter ? ` matching "${filter}"` : ''}
              </span>
              <span className="ctv-channels-spacer" />
              <IconButton
                disabled={pageNum === 0}
                onClick={() => setPageNum((current) => Math.max(0, current - 1))}
                size="sm"
                title="Previous page"
              >
                <ChevronLeft aria-hidden="true" size={15} />
              </IconButton>
              <span>
                Page <code>{pageNum + 1}</code> of <code>{totalPages}</code>
              </span>
              <IconButton
                disabled={pageNum + 1 >= totalPages}
                onClick={() => setPageNum((current) => current + 1)}
                size="sm"
                title="Next page"
              >
                <ChevronRight aria-hidden="true" size={15} />
              </IconButton>
            </div>
          </div>
        )}
      </Card>

      <div className="ctv-settings-callout">
        <Info aria-hidden="true" size={14} />
        <span>
          Logs are read from the current server log file. Older, rotated log files aren't available here — check the
          config folder's <code>logs</code> directory for history.
        </span>
      </div>
    </div>
  );
}
