/**
 * Jail management hooks.
 *
 * Provides data-fetching and mutation hooks for all jail-related views,
 * following the same patterns established by `useBans.ts` and
 * `useServerStatus.ts`.
 */

import { useCallback, useEffect, useRef, useState } from "react";
import { handleFetchError } from "../utils/fetchError";
import {
  addIgnoreIp,
  banIp,
  delIgnoreIp,
  fetchActiveBans,
  fetchJail,
  fetchJailBannedIps,
  fetchJails,
  lookupIp,
  reloadAllJails,
  reloadJail,
  setJailIdle,
  startJail,
  stopJail,
  toggleIgnoreSelf as toggleIgnoreSelfApi,
  unbanAllBans,
  unbanIp,
} from "../api/jails";
import type {
  ActiveBan,
  IpLookupResponse,
  Jail,
  JailSummary,
  UnbanAllResponse,
} from "../types/jail";

// ---------------------------------------------------------------------------
// useJails — overview list
// ---------------------------------------------------------------------------

/** Return value for {@link useJails}. */
export interface UseJailsResult {
  /** All known jails. */
  jails: JailSummary[];
  /** Total count returned by the backend. */
  total: number;
  /** `true` while a fetch is in progress. */
  loading: boolean;
  /** Error message from the last failed fetch, or `null`. */
  error: string | null;
  /** Re-fetch the jail list from the backend. */
  refresh: () => void;
  /** Start a specific jail (returns a promise for error handling). */
  startJail: (name: string) => Promise<void>;
  /** Stop a specific jail. */
  stopJail: (name: string) => Promise<void>;
  /** Toggle idle mode for a jail. */
  setIdle: (name: string, on: boolean) => Promise<void>;
  /** Reload a specific jail. */
  reloadJail: (name: string) => Promise<void>;
  /** Reload all jails at once. */
  reloadAll: () => Promise<void>;
}

/**
 * Fetch and manage the jail overview list.
 *
 * Automatically loads on mount and exposes control mutations that refresh
 * the list after each operation.
 *
 * @returns Current jail list, loading/error state, and control callbacks.
 */
export function useJails(): UseJailsResult {
  const [jails, setJails] = useState<JailSummary[]>([]);
  const [total, setTotal] = useState(0);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const abortRef = useRef<AbortController | null>(null);

  const load = useCallback(() => {
    abortRef.current?.abort();
    const ctrl = new AbortController();
    abortRef.current = ctrl;
    setLoading(true);
    setError(null);

    fetchJails()
      .then((res) => {
        if (!ctrl.signal.aborted) {
          setJails(res.jails);
          setTotal(res.total);
        }
      })
      .catch((err: unknown) => {
        if (!ctrl.signal.aborted) {
          handleFetchError(err, setError, "Failed to load jails");
        }
      })
      .finally(() => {
        if (!ctrl.signal.aborted) {
          setLoading(false);
        }
      });
  }, []);

  useEffect(() => {
    load();
    return (): void => {
      abortRef.current?.abort();
    };
  }, [load]);

  const withRefresh =
    (fn: (name: string) => Promise<unknown>) =>
    async (name: string): Promise<void> => {
      await fn(name);
      load();
    };

  return {
    jails,
    total,
    loading,
    error,
    refresh: load,
    startJail: withRefresh(startJail),
    stopJail: withRefresh(stopJail),
    setIdle: (name, on) => setJailIdle(name, on).then((): void => { load(); }),
    reloadJail: withRefresh(reloadJail),
    reloadAll: () => reloadAllJails().then((): void => { load(); }),
  };
}

// ---------------------------------------------------------------------------
// useJailDetail — single jail
// ---------------------------------------------------------------------------

/** Return value for {@link useJailDetail}. */
export interface UseJailDetailResult {
  /** Full jail configuration, or `null` while loading. */
  jail: Jail | null;
  /** Current ignore list. */
  ignoreList: string[];
  /** Whether `ignoreself` is enabled. */
  ignoreSelf: boolean;
  /** `true` while a fetch is in progress. */
  loading: boolean;
  /** Error message or `null`. */
  error: string | null;
  /** Re-fetch from the backend. */
  refresh: () => void;
  /** Add an IP to the ignore list. */
  addIp: (ip: string) => Promise<void>;
  /** Remove an IP from the ignore list. */
  removeIp: (ip: string) => Promise<void>;
  /** Enable or disable the ignoreself option for this jail. */
  toggleIgnoreSelf: (on: boolean) => Promise<void>;
  /** Start the jail. */
  start: () => Promise<void>;
  /** Stop the jail. */
  stop: () => Promise<void>;
  /** Reload jail configuration. */
  reload: () => Promise<void>;
  /** Toggle idle mode on/off for the jail. */
  setIdle: (on: boolean) => Promise<void>;
}

/**
 * Fetch and manage the detail view for a single jail.
 *
 * @param name - Jail name to load.
 * @returns Jail detail, ignore list management helpers, and fetch state.
 */
export function useJailDetail(name: string): UseJailDetailResult {
  const [jail, setJail] = useState<Jail | null>(null);
  const [ignoreList, setIgnoreList] = useState<string[]>([]);
  const [ignoreSelf, setIgnoreSelf] = useState(false);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const abortRef = useRef<AbortController | null>(null);

  const load = useCallback(() => {
    abortRef.current?.abort();
    const ctrl = new AbortController();
    abortRef.current = ctrl;
    setLoading(true);
    setError(null);

    fetchJail(name)
      .then((res) => {
        if (!ctrl.signal.aborted) {
          setJail(res.jail);
          setIgnoreList(res.ignore_list);
          setIgnoreSelf(res.ignore_self);
        }
      })
      .catch((err: unknown) => {
        if (!ctrl.signal.aborted) {
          handleFetchError(err, setError, "Failed to fetch jail detail");
        }
      })
      .finally(() => {
        if (!ctrl.signal.aborted) setLoading(false);
      });
  }, [name]);

  useEffect(() => {
    load();
    return (): void => {
      abortRef.current?.abort();
    };
  }, [load]);

  const addIp = async (ip: string): Promise<void> => {
    await addIgnoreIp(name, ip);
    load();
  };

  const removeIp = async (ip: string): Promise<void> => {
    await delIgnoreIp(name, ip);
    load();
  };

  const toggleIgnoreSelf = async (on: boolean): Promise<void> => {
    await toggleIgnoreSelfApi(name, on);
    load();
  };

  const doStart = async (): Promise<void> => {
    await startJail(name);
    load();
  };

  const doStop = async (): Promise<void> => {
    await stopJail(name);
    load();
  };

  const doReload = async (): Promise<void> => {
    await reloadJail(name);
    load();
  };

  const doSetIdle = async (on: boolean): Promise<void> => {
    await setJailIdle(name, on);
    load();
  };

  return {
    jail,
    ignoreList,
    ignoreSelf,
    loading,
    error,
    refresh: load,
    addIp,
    removeIp,
    toggleIgnoreSelf,
    start: doStart,
    stop: doStop,
    reload: doReload,
    setIdle: doSetIdle,
  };
}

// ---------------------------------------------------------------------------
// useJailBannedIps

export interface UseJailBannedIpsResult {
  items: ActiveBan[];
  total: number;
  page: number;
  pageSize: number;
  search: string;
  loading: boolean;
  error: string | null;
  opError: string | null;
  refresh: () => Promise<void>;
  setPage: (page: number) => void;
  setPageSize: (size: number) => void;
  setSearch: (term: string) => void;
  unban: (ip: string) => Promise<void>;
}

export function useJailBannedIps(jailName: string): UseJailBannedIpsResult {
  const [items, setItems] = useState<ActiveBan[]>([]);
  const [total, setTotal] = useState(0);
  const [page, setPage] = useState(1);
  const [pageSize, setPageSize] = useState(25);
  const [search, setSearch] = useState("");
  const [debouncedSearch, setDebouncedSearch] = useState("");
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const [opError, setOpError] = useState<string | null>(null);
  const debounceRef = useRef<ReturnType<typeof setTimeout> | null>(null);

  const load = useCallback(async (): Promise<void> => {
    if (!jailName) {
      setItems([]);
      setTotal(0);
      setLoading(false);
      return;
    }

    setLoading(true);
    setError(null);

    try {
      const resp = await fetchJailBannedIps(jailName, page, pageSize, debouncedSearch || undefined);
      setItems(resp.items);
      setTotal(resp.total);
    } catch (err: unknown) {
      handleFetchError(err, setError, "Failed to fetch jailed IPs");
    } finally {
      setLoading(false);
    }
  }, [jailName, page, pageSize, debouncedSearch]);

  useEffect(() => {
    if (debounceRef.current !== null) {
      clearTimeout(debounceRef.current);
    }
    debounceRef.current = setTimeout(() => {
      setDebouncedSearch(search);
      setPage(1);
    }, 300);

    // eslint-disable-next-line @typescript-eslint/explicit-function-return-type
    return () => {
      if (debounceRef.current !== null) {
        clearTimeout(debounceRef.current);
      }
    };
  }, [search]);

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

  const unban = useCallback(async (ip: string): Promise<void> => {
    setOpError(null);
    try {
      await unbanIp(ip, jailName);
      await load();
    } catch (err: unknown) {
      setOpError(err instanceof Error ? err.message : String(err));
    }
  }, [jailName, load]);

  return {
    items,
    total,
    page,
    pageSize,
    search,
    loading,
    error,
    opError,
    refresh: load,
    setPage,
    setPageSize,
    setSearch,
    unban,
  };
}

// ---------------------------------------------------------------------------
// useActiveBans — live ban list
// ---------------------------------------------------------------------------

/** Return value for {@link useActiveBans}. */
export interface UseActiveBansResult {
  /** All currently active bans. */
  bans: ActiveBan[];
  /** Total ban count. */
  total: number;
  /** `true` while fetching. */
  loading: boolean;
  /** Error message or `null`. */
  error: string | null;
  /** Re-fetch the active bans. */
  refresh: () => void;
  /** Ban an IP in a specific jail. */
  banIp: (jail: string, ip: string) => Promise<void>;
  /** Unban an IP from a jail (or all jails when `jail` is omitted). */
  unbanIp: (ip: string, jail?: string) => Promise<void>;
  /** Unban every currently banned IP across all jails. */
  unbanAll: () => Promise<UnbanAllResponse>;
}

/**
 * Fetch and manage the currently-active ban list.
 *
 * @returns Active ban list, mutation callbacks, and fetch state.
 */
export function useActiveBans(): UseActiveBansResult {
  const [bans, setBans] = useState<ActiveBan[]>([]);
  const [total, setTotal] = useState(0);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const abortRef = useRef<AbortController | null>(null);

  const load = useCallback(() => {
    abortRef.current?.abort();
    const ctrl = new AbortController();
    abortRef.current = ctrl;
    setLoading(true);
    setError(null);

    fetchActiveBans()
      .then((res) => {
        if (!ctrl.signal.aborted) {
          setBans(res.bans);
          setTotal(res.total);
        }
      })
      .catch((err: unknown) => {
        if (!ctrl.signal.aborted) {
          handleFetchError(err, setError, "Failed to fetch active bans");
        }
      })
      .finally(() => {
        if (!ctrl.signal.aborted) setLoading(false);
      });
  }, []);

  useEffect(() => {
    load();
    return (): void => {
      abortRef.current?.abort();
    };
  }, [load]);

  const doBan = async (jail: string, ip: string): Promise<void> => {
    await banIp(jail, ip);
    load();
  };

  const doUnban = async (ip: string, jail?: string): Promise<void> => {
    await unbanIp(ip, jail);
    load();
  };

  const doUnbanAll = async (): Promise<UnbanAllResponse> => {
    const result = await unbanAllBans();
    load();
    return result;
  };

  return {
    bans,
    total,
    loading,
    error,
    refresh: load,
    banIp: doBan,
    unbanIp: doUnban,
    unbanAll: doUnbanAll,
  };
}

// ---------------------------------------------------------------------------
// useIpLookup — single IP lookup
// ---------------------------------------------------------------------------

/** Return value for {@link useIpLookup}. */
export interface UseIpLookupResult {
  /** Lookup result, or `null` when no lookup has been performed yet. */
  result: IpLookupResponse | null;
  /** `true` while a lookup is in progress. */
  loading: boolean;
  /** Error message or `null`. */
  error: string | null;
  /** Trigger an IP lookup. */
  lookup: (ip: string) => void;
  /** Clear the result and error state. */
  clear: () => void;
}

/**
 * Manage IP lookup state (lazy — no fetch on mount).
 *
 * @returns Lookup result, state flags, and a `lookup` trigger callback.
 */
export function useIpLookup(): UseIpLookupResult {
  const [result, setResult] = useState<IpLookupResponse | null>(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);

  const lookup = useCallback((ip: string) => {
    setLoading(true);
    setError(null);
    setResult(null);

    lookupIp(ip)
      .then((res) => {
        setResult(res);
      })
      .catch((err: unknown) => {
        handleFetchError(err, setError, "Failed to lookup IP");
      })
      .finally(() => {
        setLoading(false);
      });
  }, []);

  const clear = useCallback(() => {
    setResult(null);
    setError(null);
  }, []);

  return { result, loading, error, lookup, clear };
}
