/**
 * ServerHealthSection — service health panel and log viewer for ServerTab.
 *
 * Renders two sections:
 *  1. **Service Health panel** — shows online/offline state, version, active
 *     jail count, total bans, total failures, log level, and log target.
 *  2. **Log viewer** — displays the tail of the fail2ban daemon log file with
 *     toolbar controls for line count, substring filter, manual refresh, and
 *     optional auto-refresh. Log lines are color-coded by severity.
 */

import {
  useCallback,
  useEffect,
  useRef,
  useState,
} from "react";
import {
  Badge,
  Button,
  Field,
  Input,
  MessageBar,
  MessageBarBody,
  Select,
  Spinner,
  Switch,
  Text,
  makeStyles,
  tokens,
} from "@fluentui/react-components";
import {
  ArrowClockwise24Regular,
  DocumentBulletList24Regular,
  Filter24Regular,
} from "@fluentui/react-icons";
import { useServerHealth } from "../../hooks/useServerHealth";
import { useConfigStyles } from "./configStyles";

// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------

/** Available line-count options for the log tail. */
const LINE_COUNT_OPTIONS: number[] = [100, 200, 500, 1000];

/** Auto-refresh interval options in seconds. */
const AUTO_REFRESH_INTERVALS: { label: string; value: number }[] = [
  { label: "5 s", value: 5 },
  { label: "10 s", value: 10 },
  { label: "30 s", value: 30 },
];

/** Debounce delay for the filter input in milliseconds. */
const FILTER_DEBOUNCE_MS = 300;

/** Log targets that are not file paths — file-based viewing is unavailable. */
const NON_FILE_TARGETS = new Set(["STDOUT", "STDERR", "SYSLOG", "SYSTEMD-JOURNAL"]);

// ---------------------------------------------------------------------------
// Styles
// ---------------------------------------------------------------------------

const useStyles = makeStyles({
  healthGrid: {
    display: "grid",
    gridTemplateColumns: "repeat(auto-fill, minmax(120px, 1fr))",
    gap: tokens.spacingHorizontalM,
    marginTop: tokens.spacingVerticalM,
  },
  statCard: {
    backgroundColor: tokens.colorNeutralBackground3,
    borderRadius: tokens.borderRadiusMedium,
    padding: `${tokens.spacingVerticalS} ${tokens.spacingHorizontalM}`,
    display: "flex",
    flexDirection: "column",
    gap: tokens.spacingVerticalXS,
  },
  statLabel: {
    color: tokens.colorNeutralForeground3,
    fontSize: tokens.fontSizeBase200,
  },
  statValue: {
    fontWeight: tokens.fontWeightSemibold,
    fontSize: tokens.fontSizeBase300,
  },
  metaRow: {
    display: "flex",
    gap: tokens.spacingHorizontalL,
    marginTop: tokens.spacingVerticalS,
    flexWrap: "wrap",
  },
  metaItem: {
    display: "flex",
    flexDirection: "column",
    gap: "2px",
  },
  toolbar: {
    display: "flex",
    alignItems: "center",
    gap: tokens.spacingHorizontalM,
    flexWrap: "wrap",
    marginBottom: tokens.spacingVerticalM,
  },
  filterInput: {
    width: "200px",
  },
  logContainer: {
    backgroundColor: tokens.colorNeutralBackground4,
    borderRadius: tokens.borderRadiusMedium,
    padding: `${tokens.spacingVerticalM} ${tokens.spacingHorizontalM}`,
    maxHeight: "560px",
    overflowY: "auto",
    fontFamily: "monospace",
    fontSize: tokens.fontSizeBase200,
    lineHeight: "1.6",
    border: `1px solid ${tokens.colorNeutralStroke2}`,
  },
  logLineError: {
    color: tokens.colorPaletteRedForeground1,
  },
  logLineWarning: {
    color: tokens.colorPaletteYellowForeground1,
  },
  logLineDebug: {
    color: tokens.colorNeutralForeground4,
  },
  logLineDefault: {
    color: tokens.colorNeutralForeground1,
  },
  truncatedBanner: {
    marginBottom: tokens.spacingVerticalS,
    color: tokens.colorNeutralForeground3,
    fontSize: tokens.fontSizeBase100,
  },
  emptyLog: {
    color: tokens.colorNeutralForeground3,
    fontStyle: "italic",
    padding: tokens.spacingVerticalS,
  },
});

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

/**
 * Determine the CSS class key for a log line based on its severity.
 *
 * fail2ban formats log lines as:
 *   ``2025-… fail2ban.filter [PID]: LEVEL   message``
 *
 * @param line - A single log line string.
 * @returns The severity key: "error" | "warning" | "debug" | "default".
 */
function detectSeverity(line: string): "error" | "warning" | "debug" | "default" {
  const upper = line.toUpperCase();
  if (upper.includes(" ERROR ") || upper.includes(" CRITICAL ")) return "error";
  if (upper.includes(" WARNING ") || upper.includes(": WARNING") || upper.includes(" WARN ")) return "warning";
  if (upper.includes(" DEBUG ")) return "debug";
  return "default";
}

// ---------------------------------------------------------------------------
// Component
// ---------------------------------------------------------------------------

/**
 * Server health panel and log viewer section for ServerTab.
 *
 * @returns JSX element.
 */
export function ServerHealthSection(): React.JSX.Element {
  const configStyles = useConfigStyles();
  const styles = useStyles();

  // ---- data state ----------------------------------------------------------
  const [loading, setLoading] = useState(true);
  const [isRefreshing, setIsRefreshing] = useState(false);

  // ---- toolbar state -------------------------------------------------------
  const [linesCount, setLinesCount] = useState<number>(200);
  const [filterRaw, setFilterRaw] = useState<string>("");
  const [filterValue, setFilterValue] = useState<string>("");
  const { status, logData, error, refresh } = useServerHealth(linesCount, filterValue);
  const [autoRefresh, setAutoRefresh] = useState(false);
  const [refreshInterval, setRefreshInterval] = useState(10);

  // ---- refs ----------------------------------------------------------------
  const logContainerRef = useRef<HTMLDivElement>(null);
  const filterDebounceRef = useRef<ReturnType<typeof setTimeout> | null>(null);
  const autoRefreshTimerRef = useRef<ReturnType<typeof setInterval> | null>(null);

  // ---- scroll helper -------------------------------------------------------
  const scrollToBottom = useCallback((): void => {
    if (logContainerRef.current) {
      logContainerRef.current.scrollTop = logContainerRef.current.scrollHeight;
    }
  }, []);

  // ---- fetch logic ---------------------------------------------------------
  const fetchData = useCallback(
    async (showSpinner: boolean): Promise<void> => {
      if (showSpinner) {
        setIsRefreshing(true);
      }

      try {
        await refresh();
      } finally {
        if (showSpinner) {
          setIsRefreshing(false);
        }
        setLoading(false);
      }
    },
    [refresh],
  );

  // ---- initial load --------------------------------------------------------
  useEffect(() => {
    setLoading(true);
    void fetchData(false);
  }, [fetchData]);

  // ---- scroll to bottom when new log data arrives -------------------------
  useEffect(() => {
    if (logData) {
      // Tiny timeout lets the DOM paint before scrolling.
      const t = setTimeout(scrollToBottom, 50);
      return (): void => { clearTimeout(t); };
    }
  }, [logData, scrollToBottom]);

  // ---- auto-refresh interval ----------------------------------------------
  useEffect(() => {
    if (autoRefreshTimerRef.current) {
      clearInterval(autoRefreshTimerRef.current);
      autoRefreshTimerRef.current = null;
    }
    if (autoRefresh) {
      autoRefreshTimerRef.current = setInterval(() => {
        void fetchData(true);
      }, refreshInterval * 1000);
    }
    return (): void => {
      if (autoRefreshTimerRef.current) {
        clearInterval(autoRefreshTimerRef.current);
      }
    };
  }, [autoRefresh, refreshInterval, fetchData]);

  // ---- filter debounce ----------------------------------------------------
  const handleFilterChange = useCallback((value: string): void => {
    setFilterRaw(value);
    if (filterDebounceRef.current) clearTimeout(filterDebounceRef.current);
    filterDebounceRef.current = setTimeout(() => {
      setFilterValue(value);
    }, FILTER_DEBOUNCE_MS);
  }, []);

  // ---- render helpers ------------------------------------------------------
  const renderLogLine = (line: string, idx: number): React.JSX.Element => {
    const severity = detectSeverity(line);
    const className =
      severity === "error"
        ? styles.logLineError
        : severity === "warning"
          ? styles.logLineWarning
          : severity === "debug"
            ? styles.logLineDebug
            : styles.logLineDefault;
    return (
      <div key={idx} className={className}>
        {line}
      </div>
    );
  };

  // ---- loading state -------------------------------------------------------
  if (loading) {
    return <Spinner label="Loading log viewer…" />;
  }

  // ---- error state ---------------------------------------------------------
  if (error && !status && !logData) {
    return (
      <MessageBar intent="error">
        <MessageBarBody>{error}</MessageBarBody>
      </MessageBar>
    );
  }

  const isNonFileTarget =
    logData?.log_target != null &&
    NON_FILE_TARGETS.has(logData.log_target.toUpperCase());

  const isTruncated =
    logData != null && logData.total_lines > logData.lines.length;

  return (
    <>
      {/* Service Health Panel */}
      <div className={configStyles.sectionCard}>
        <div style={{ display: "flex", alignItems: "center", gap: tokens.spacingHorizontalM }}>
          <DocumentBulletList24Regular />
          <Text weight="semibold" size={400}>
            Service Health
          </Text>
          {status?.online ? (
            <Badge appearance="filled" color="success">
              Running
            </Badge>
          ) : (
            <Badge appearance="filled" color="danger">
              Offline
            </Badge>
          )}
        </div>

        {status && !status.online && (
          <MessageBar intent="warning" style={{ marginTop: tokens.spacingVerticalM }}>
            <MessageBarBody>
              fail2ban is not running or unreachable. Check the server and socket
              configuration.
            </MessageBarBody>
          </MessageBar>
        )}

        {status?.online && (
          <>
            <div className={styles.healthGrid}>
              {status.version && (
                <div className={styles.statCard}>
                  <Text className={styles.statLabel}>Version</Text>
                  <Text className={styles.statValue}>{status.version}</Text>
                </div>
              )}
              <div className={styles.statCard}>
                <Text className={styles.statLabel}>Active Jails</Text>
                <Text className={styles.statValue}>{status.jail_count}</Text>
              </div>
              <div className={styles.statCard}>
                <Text className={styles.statLabel}>Currently Banned</Text>
                <Text className={styles.statValue}>{status.total_bans}</Text>
              </div>
              <div className={styles.statCard}>
                <Text className={styles.statLabel}>Currently Failed</Text>
                <Text className={styles.statValue}>{status.total_failures}</Text>
              </div>
            </div>

            <div className={styles.metaRow}>
              <div className={styles.metaItem}>
                <Text className={styles.statLabel}>Log Level</Text>
                <Text size={300}>{status.log_level}</Text>
              </div>
              <div className={styles.metaItem}>
                <Text className={styles.statLabel}>Log Target</Text>
                <Text size={300}>{status.log_target}</Text>
              </div>
            </div>
          </>
        )}
      </div>

      {/* Log Viewer */}
      <div className={configStyles.sectionCard}>
        <div style={{ display: "flex", alignItems: "center", gap: tokens.spacingHorizontalM, marginBottom: tokens.spacingVerticalM }}>
          <Text weight="semibold" size={400}>
            Log Viewer
          </Text>
          {logData && (
            <Text size={200} style={{ color: tokens.colorNeutralForeground3 }}>
              {logData.log_path}
            </Text>
          )}
        </div>

        {/* Non-file target info banner */}
        {isNonFileTarget && (
          <MessageBar intent="info" style={{ marginBottom: tokens.spacingVerticalM }}>
            <MessageBarBody>
              fail2ban is logging to <strong>{logData.log_target}</strong>.
              File-based log viewing is not available.
            </MessageBarBody>
          </MessageBar>
        )}

        {/* Toolbar — only shown when log data is available */}
        {!isNonFileTarget && (
          <>
            <div className={styles.toolbar}>
              {/* Filter input */}
              <Field label="Filter">
                <Input
                  className={styles.filterInput}
                  type="text"
                  value={filterRaw}
                  contentBefore={<Filter24Regular />}
                  placeholder="Substring filter…"
                  onChange={(_e, d) => { handleFilterChange(d.value); }}
                />
              </Field>

              {/* Lines count selector */}
              <Field label="Lines">
                <Select
                  value={String(linesCount)}
                  onChange={(_e, d) => { setLinesCount(Number(d.value)); }}
                >
                  {LINE_COUNT_OPTIONS.map((n) => (
                    <option key={n} value={String(n)}>
                      {n}
                    </option>
                  ))}
                </Select>
              </Field>

              {/* Manual refresh */}
              <div style={{ alignSelf: "flex-end" }}>
                <Button
                  icon={<ArrowClockwise24Regular />}
                  appearance="secondary"
                  onClick={() => void fetchData(true)}
                  disabled={isRefreshing}
                >
                  {isRefreshing ? "Refreshing…" : "Refresh"}
                </Button>
              </div>

              {/* Auto-refresh toggle */}
              <div style={{ alignSelf: "flex-end" }}>
                <Switch
                  label="Auto-refresh"
                  checked={autoRefresh}
                  onChange={(_e, d) => { setAutoRefresh(d.checked); }}
                />
              </div>

              {/* Auto-refresh interval selector */}
              {autoRefresh && (
                <Field label="Interval">
                  <Select
                    value={String(refreshInterval)}
                    onChange={(_e, d) => { setRefreshInterval(Number(d.value)); }}
                  >
                    {AUTO_REFRESH_INTERVALS.map((opt) => (
                      <option key={opt.value} value={String(opt.value)}>
                        {opt.label}
                      </option>
                    ))}
                  </Select>
                </Field>
              )}
            </div>

            {/* Truncation notice */}
            {isTruncated && (
              <Text className={styles.truncatedBanner} block>
                Showing last {logData.lines.length} of {logData.total_lines} lines.
                Increase the line count or use the filter to narrow results.
              </Text>
            )}

            {/* Log lines container */}
            <div className={styles.logContainer} ref={logContainerRef}>
              {isRefreshing && (
                <div style={{ marginBottom: tokens.spacingVerticalS }}>
                  <Spinner size="tiny" label="Refreshing…" />
                </div>
              )}

              {logData && logData.lines.length === 0 ? (
                <div className={styles.emptyLog}>
                  {filterValue
                    ? `No lines match the filter "${filterValue}".`
                    : "No log entries found."}
                </div>
              ) : (
                logData?.lines.map((line, idx) => renderLogLine(line, idx))
              )}
            </div>

            {/* General fetch error */}
            {error && (
              <MessageBar intent="error" style={{ marginTop: tokens.spacingVerticalM }}>
                <MessageBarBody>{error}</MessageBarBody>
              </MessageBar>
            )}
          </>
        )}
      </div>
    </>
  );
}
