import { useEffect, useMemo, useState } from 'react';
import { Button } from '@/components/ui/button';
import { Sparkline } from '@/components/ui/sparkline';
import { AlertCircle, ArrowUp, ArrowDown, ChevronLeft, ChevronRight, CircleArrowUp, Layers, RefreshCw } from 'lucide-react';
import { cn } from '@/lib/utils';
import { Skeleton } from '@/components/ui/skeleton';
import type { StackStatusEntry, MetricPoint, StackCpuSeries, StackStatusesLoadStatus } from './types';
import type { StackUpdateInfo } from '@/types/imageUpdates';
import { isConfirmedImageUpdate, isConfirmedServiceUpdate } from '@/types/imageUpdates';
import { aggregateCurrentUsage } from './aggregateCurrentUsage';
import { classifyRow, type RowState } from './classifyRow';
import { updateAvailableLabel } from '@/lib/updateAvailableLabel';
import GitOpsBadge from '@/components/gitops/GitOpsBadge';
import type { GitOpsSourceStateMap } from './useGitOpsSourceStates';

interface StackHealthTableProps {
  stackStatuses: Record<string, StackStatusEntry>;
  stackStatusesLoadStatus: StackStatusesLoadStatus;
  stackStatusesLoadError: string | null;
  onRetryStackStatuses?: () => void;
  metrics: MetricPoint[];
  stackCpuSeries: Record<string, StackCpuSeries>;
  onNavigateToStack: (stackFile: string) => void;
  stackUpdates?: Record<string, StackUpdateInfo>;
  /**
   * GitOps source state per stack name. A stack the model says nothing about
   * is absent, and its SOURCE cell keeps the plain Git or Local label.
   */
  gitopsSourceStates?: GitOpsSourceStateMap;
}

type SortKey = 'stack' | 'up' | 'cpu' | 'mem';

const PAGE_SIZE = 8;
// Shared by the header and data rows so their columns stay aligned. The
// `max-md:min-w` keeps both at the same width below md, where the card scrolls
// horizontally; desktop is unaffected by the `max-md:` prefix. Columns:
// STACK · SOURCE · PORT · UP · CPU · MEM · CPU·10m · chevron.
const GRID_TEMPLATE = 'grid-cols-[minmax(0,1fr)_64px_56px_52px_52px_72px_110px_16px] max-md:min-w-[620px]';

const formatMemory = (mb: number): string => {
  if (mb >= 1024) return `${(mb / 1024).toFixed(1)} GB`;
  return `${mb.toFixed(0)} MB`;
};

// Grid-compatible sortable header cell. Renders a <button> inside the grid
// <span> (the security ImagesTab pattern uses <TableHead>, which is invalid
// inside this CSS-grid layout, so only the sort logic is shared here).
function SortHeader({ label, k, sortKey, sortDir, onSort, align = 'left' }: {
  label: string;
  k: SortKey;
  sortKey: SortKey | null;
  sortDir: 'asc' | 'desc';
  onSort: (k: SortKey) => void;
  align?: 'left' | 'right';
}) {
  return (
    <span className={align === 'right' ? 'text-right' : undefined}>
      <button
        type="button"
        onClick={() => onSort(k)}
        className={cn(
          'inline-flex items-center gap-1 uppercase tracking-[0.22em] hover:text-stat-value',
          align === 'right' && 'flex-row-reverse',
        )}
      >
        {label}
        {sortKey === k && (sortDir === 'asc' ? <ArrowUp className="h-2.5 w-2.5" /> : <ArrowDown className="h-2.5 w-2.5" />)}
      </button>
    </span>
  );
}

function formatUptime(seconds: number): string {
  if (!Number.isFinite(seconds) || seconds <= 0) return '--';
  const days = Math.floor(seconds / 86400);
  if (days > 0) return `${days}d`;
  const hours = Math.floor(seconds / 3600);
  if (hours > 0) return `${hours}h`;
  const minutes = Math.floor(seconds / 60);
  if (minutes > 0) return `${minutes}m`;
  return `${Math.max(1, Math.floor(seconds))}s`;
}

const rowTint: Record<RowState, string> = {
  healthy: '',
  warn: 'bg-warning/[0.04]',
  error: 'bg-destructive/[0.04]',
};

const sparkStroke: Record<RowState, string> = {
  healthy: 'var(--chart-1)',
  warn: 'var(--warning)',
  error: 'var(--destructive)',
};

export function StackHealthTable({
  stackStatuses,
  stackStatusesLoadStatus,
  stackStatusesLoadError,
  onRetryStackStatuses,
  metrics,
  stackCpuSeries,
  onNavigateToStack,
  stackUpdates = {},
  gitopsSourceStates = {},
}: StackHealthTableProps) {
  const [page, setPage] = useState(0);
  // null = the default health-state ordering (worst first); a SortKey switches
  // to user-driven column sort.
  const [sortKey, setSortKey] = useState<SortKey | null>(null);
  const [sortDir, setSortDir] = useState<'asc' | 'desc'>('desc');
  // Live-tick the current second so uptime labels advance without a parent
  // refetch. Thirty-second cadence keeps the DOM calm while still refreshing
  // every "Nm" bucket change.
  const [now, setNow] = useState(() => Date.now());
  useEffect(() => {
    const id = setInterval(() => setNow(Date.now()), 30000);
    return () => clearInterval(id);
  }, []);

  const stackAggregates = useMemo(() => aggregateCurrentUsage(metrics), [metrics]);

  const baseRows = useMemo(() => {
    return Object.entries(stackStatuses).map(([file, entry]) => {
      const name = file.replace(/\.(yml|yaml)$/, '');
      const agg = stackAggregates[name];
      const series = stackCpuSeries[name];
      const peakCpu = series?.peakValue ?? agg?.cpu ?? 0;
      const state = classifyRow(entry.status, peakCpu);
      const updateInfo = stackUpdates[file];
      return {
        file,
        name,
        status: entry.status,
        memory: agg?.mem ?? null,
        cpu: agg?.cpu ?? null,
        peakCpu,
        series: series?.points ?? [],
        peakIndex: series?.peakIndex ?? -1,
        state,
        runningSince: entry.runningSince ?? null,
        source: entry.source ?? 'local',
        mainPort: entry.mainPort ?? null,
        hasUpdate: updateInfo != null && isConfirmedImageUpdate(updateInfo),
        outdatedServices: (updateInfo?.services ?? [])
          .filter((s) => isConfirmedServiceUpdate(s))
          .map((s) => s.service),
      };
    });
  }, [stackStatuses, stackAggregates, stackCpuSeries, stackUpdates]);

  const rows = useMemo(() => {
    const list = [...baseRows];
    if (sortKey === null) {
      const stateOrder: Record<RowState, number> = { error: 0, warn: 1, healthy: 2 };
      list.sort((a, b) => {
        const diff = stateOrder[a.state] - stateOrder[b.state];
        if (diff !== 0) return diff;
        return b.peakCpu - a.peakCpu;
      });
      return list;
    }
    const dir = sortDir === 'asc' ? 1 : -1;
    const nowSecs = now / 1000;
    const uptime = (rs: number | null) => (rs !== null ? nowSecs - rs : -1);
    list.sort((a, b) => {
      switch (sortKey) {
        case 'stack': return a.name.localeCompare(b.name) * dir;
        case 'up': return (uptime(a.runningSince) - uptime(b.runningSince)) * dir;
        case 'cpu': return ((a.cpu ?? -1) - (b.cpu ?? -1)) * dir;
        case 'mem': return ((a.memory ?? -1) - (b.memory ?? -1)) * dir;
        // Exhaustive: a new SortKey must add a case or this fails to compile.
        default: { const _exhaustive: never = sortKey; return _exhaustive; }
      }
    });
    return list;
  }, [baseRows, sortKey, sortDir, now]);

  const toggleSort = (key: SortKey) => {
    if (sortKey === key) setSortDir((d) => (d === 'asc' ? 'desc' : 'asc'));
    else { setSortKey(key); setSortDir(key === 'stack' ? 'asc' : 'desc'); }
    setPage(0);
  };

  const totalPages = Math.max(1, Math.ceil(rows.length / PAGE_SIZE));
  const safePage = Math.min(page, totalPages - 1);
  const pagedRows = rows.slice(safePage * PAGE_SIZE, (safePage + 1) * PAGE_SIZE);
  const needsPagination = rows.length > PAGE_SIZE;

  const stackCount = Object.keys(stackStatuses).length;

  if (stackStatusesLoadStatus === 'idle' || stackStatusesLoadStatus === 'loading') {
    return (
      <div className="rounded-lg border border-card-border border-t-card-border-top bg-card shadow-card-bevel p-5 space-y-3">
        <Skeleton className="h-6 w-40" />
        <Skeleton className="h-10 w-full" />
        <Skeleton className="h-10 w-full" />
        <Skeleton className="h-10 w-full" />
      </div>
    );
  }

  if (stackStatusesLoadStatus === 'error') {
    return (
      <div className="rounded-lg border border-card-border border-t-card-border-top bg-card shadow-card-bevel py-10">
        <div className="flex flex-col items-center justify-center gap-3 text-stat-subtitle">
          <AlertCircle className="h-8 w-8 text-stat-icon" strokeWidth={1.5} aria-hidden />
          <p className="text-sm text-center px-4">
            {stackStatusesLoadError ?? 'Could not load stack health.'}
          </p>
          {onRetryStackStatuses && (
            <Button type="button" variant="outline" size="sm" onClick={onRetryStackStatuses}>
              <RefreshCw className="w-4 h-4" />
              Retry
            </Button>
          )}
        </div>
      </div>
    );
  }

  if (stackCount === 0) {
    return (
      <div className="rounded-lg border border-card-border border-t-card-border-top bg-card shadow-card-bevel py-10">
        <div className="flex flex-col items-center justify-center gap-2 text-stat-subtitle">
          <Layers className="h-8 w-8 text-stat-icon" strokeWidth={1.5} />
          <p className="text-sm">No stacks found. Create one from the sidebar.</p>
        </div>
      </div>
    );
  }

  return (
    <div className="rounded-lg border border-card-border border-t-card-border-top bg-card shadow-card-bevel max-md:overflow-x-auto">
      <div className="flex items-center justify-between gap-4 px-5 py-4">
        <div className="flex items-baseline gap-3">
          <h2 className="font-heading text-xl leading-none tracking-tight text-stat-value">
            Stack health
          </h2>
          <span className="font-mono text-[11px] uppercase tracking-[0.22em] text-stat-subtitle">
            {stackCount} {stackCount === 1 ? 'stack' : 'stacks'}{sortKey === null ? ' · sorted by load' : ''}
          </span>
        </div>
        {needsPagination ? (
          <div className="flex items-center gap-1.5">
            <Button
              variant="ghost"
              size="icon"
              className="h-6 w-6"
              disabled={safePage === 0}
              onClick={() => setPage(safePage - 1)}
            >
              <ChevronLeft className="h-3.5 w-3.5" strokeWidth={1.5} />
            </Button>
            <span className="text-xs font-mono tabular-nums text-stat-subtitle min-w-[3rem] text-center">
              {safePage + 1} / {totalPages}
            </span>
            <Button
              variant="ghost"
              size="icon"
              className="h-6 w-6"
              disabled={safePage >= totalPages - 1}
              onClick={() => setPage(safePage + 1)}
            >
              <ChevronRight className="h-3.5 w-3.5" strokeWidth={1.5} />
            </Button>
          </div>
        ) : null}
      </div>
      <div className={`grid ${GRID_TEMPLATE} items-center gap-4 border-t border-border/60 px-[var(--density-row-x)] py-[var(--density-cell-y)] font-mono text-[10px] uppercase tracking-[0.22em] text-stat-subtitle`}>
        <SortHeader label="STACK" k="stack" sortKey={sortKey} sortDir={sortDir} onSort={toggleSort} />
        <span>SOURCE</span>
        <span>PORT</span>
        <SortHeader label="UP" k="up" sortKey={sortKey} sortDir={sortDir} onSort={toggleSort} align="right" />
        <SortHeader label="CPU" k="cpu" sortKey={sortKey} sortDir={sortDir} onSort={toggleSort} align="right" />
        <SortHeader label="MEM" k="mem" sortKey={sortKey} sortDir={sortDir} onSort={toggleSort} align="right" />
        <span className="text-right">CPU · 10m</span>
        <span />
      </div>
      <ul className="divide-y divide-border/40">
        {pagedRows.map((row) => {
          const updateLabel = row.hasUpdate ? updateAvailableLabel(row.outdatedServices) : null;
          // Looked up here rather than folded into the memoized rows: it is
          // presentation, and threading it through would make the row memo
          // recompute on every parent render for no benefit.
          const gitopsSourceState = gitopsSourceStates[row.name];
          return (
          <li
            key={row.file}
            role="button"
            tabIndex={0}
            onClick={() => onNavigateToStack(row.file)}
            onKeyDown={(e) => {
              if (e.key === 'Enter' || e.key === ' ') {
                e.preventDefault();
                onNavigateToStack(row.file);
              }
            }}
            className={`grid ${GRID_TEMPLATE} cursor-pointer items-center gap-4 px-[var(--density-row-x)] py-[var(--density-row-y)] transition-colors hover:bg-accent/5 ${rowTint[row.state]}`}
          >
            <span className="flex items-center gap-1.5 min-w-0">
              <span className="min-w-0 truncate font-mono text-sm text-stat-value">{row.name}</span>
              {updateLabel && (
                <span className="shrink-0" title={updateLabel}>
                  <CircleArrowUp
                    className="h-3.5 w-3.5 text-brand"
                    strokeWidth={2}
                    aria-label={updateLabel}
                  />
                </span>
              )}
              {gitopsSourceState && (
                <GitOpsBadge facet="source" status={gitopsSourceState} className="shrink-0" />
              )}
            </span>
            <span className="truncate font-mono text-[11px] uppercase tracking-wide text-stat-subtitle">
              {row.source === 'git' ? 'Git' : 'Local'}
            </span>
            <span className="truncate font-mono text-xs tabular-nums text-stat-subtitle">
              {row.mainPort !== null ? row.mainPort : '--'}
            </span>
            <span className="text-right font-mono text-xs tabular-nums text-stat-subtitle">
              {row.runningSince !== null
                ? formatUptime(Math.max(0, Math.floor(now / 1000 - row.runningSince)))
                : '--'}
            </span>
            <span className="text-right font-mono text-xs tabular-nums text-stat-subtitle">
              {row.cpu !== null ? `${row.cpu.toFixed(0)}%` : '--'}
            </span>
            <span className="text-right font-mono text-xs tabular-nums text-stat-subtitle">
              {row.memory !== null ? formatMemory(row.memory) : '--'}
            </span>
            <span className="ml-auto block h-5 w-[110px]">
              {row.series.length > 1 ? (
                <Sparkline
                  points={row.series}
                  stroke={sparkStroke[row.state]}
                  fill={sparkStroke[row.state]}
                  peakColor="var(--chart-2)"
                  peakIndex={row.peakIndex >= 0 ? row.peakIndex : undefined}
                  showPeak={row.state !== 'healthy'}
                />
              ) : (
                <span className="block h-full w-full border-b border-dashed border-border/60" />
              )}
            </span>
            <ChevronRight className="h-3.5 w-3.5 text-stat-icon" strokeWidth={1.5} />
          </li>
          );
        })}
      </ul>
    </div>
  );
}
