import { useState, useCallback, useRef, useEffect } from 'react';
import pb from '../lib/pocketbase';
import { apiUrl, authHeaders } from '../lib/api';
import { trackEvent } from '../lib/analytics';

export interface SavedSearch {
  id: string;
  name: string;
  params: string;
  screenshotUrl: string;
  notes: string;
  created: string;
}

// Exponential backoff: 2s, 3s, 4s, 6s, 8s, 12s, ... capped at 15s.
// Caps total wait under a minute while staying responsive for fast jobs.
const POLL_BASE_MS = 2000;
const POLL_MAX_MS = 15000;
const POLL_BACKOFF = 1.5;
const MAX_POLL_ATTEMPTS = 8;

function nextPollDelay(attempt: number): number {
  return Math.min(POLL_MAX_MS, Math.round(POLL_BASE_MS * Math.pow(POLL_BACKOFF, attempt)));
}

export function useSavedSearches(userId: string | null) {
  const [searches, setSearches] = useState<SavedSearch[]>([]);
  const [loading, setLoading] = useState(false);
  const [saving, setSaving] = useState(false);
  const [error, setError] = useState<string | null>(null);

  const pollTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
  const pollAttemptsRef = useRef(0);
  const pollInFlightRef = useRef(false);
  const isMountedRef = useRef(true);
  const userIdRef = useRef(userId);
  userIdRef.current = userId;

  const stopPolling = useCallback(() => {
    if (pollTimerRef.current) {
      clearTimeout(pollTimerRef.current);
      pollTimerRef.current = null;
    }
    pollAttemptsRef.current = 0;
  }, []);

  // Clean up polling on unmount or userId change
  useEffect(() => stopPolling, [userId, stopPolling]);

  // Mark the hook as unmounted so late-arriving async work doesn't touch state
  useEffect(() => {
    isMountedRef.current = true;
    return () => {
      isMountedRef.current = false;
      stopPolling();
    };
  }, [stopPolling]);

  const fetchRecords = useCallback(async (uid: string): Promise<SavedSearch[]> => {
    const records = await pb.collection('saved_searches').getFullList({
      sort: '-created',
      filter: `user = "${uid}"`,
    });
    return records.map((r) => ({
      id: r.id,
      name: (r as Record<string, unknown>).name as string,
      params: (r as Record<string, unknown>).params as string,
      screenshotUrl: (r as Record<string, unknown>).screenshot
        ? pb.files.getURL(r, (r as Record<string, unknown>).screenshot as string)
        : '',
      notes: ((r as Record<string, unknown>).notes as string) || '',
      created: r.created,
    }));
  }, []);

  const startPolling = useCallback(() => {
    if (pollTimerRef.current) return;
    pollAttemptsRef.current = 0;
    pollInFlightRef.current = false;

    const scheduleNext = () => {
      if (!isMountedRef.current) return;
      const delay = nextPollDelay(pollAttemptsRef.current);
      pollTimerRef.current = setTimeout(tick, delay);
    };

    const tick = async () => {
      pollTimerRef.current = null;
      if (pollInFlightRef.current) {
        scheduleNext();
        return;
      }
      const uid = userIdRef.current;
      if (!uid) return;
      pollAttemptsRef.current++;
      if (pollAttemptsRef.current > MAX_POLL_ATTEMPTS) return;
      pollInFlightRef.current = true;
      try {
        const mapped = await fetchRecords(uid);
        if (!isMountedRef.current) return;
        setSearches(mapped);
        if (!mapped.some((s) => !s.screenshotUrl)) return;
        scheduleNext();
      } catch {
        // Silent — background poll errors don't surface to UI; keep trying.
        if (isMountedRef.current) scheduleNext();
      } finally {
        pollInFlightRef.current = false;
      }
    };

    scheduleNext();
  }, [fetchRecords]);

  const fetchSearches = useCallback(async () => {
    if (!userId) return;
    setLoading(true);
    setError(null);
    try {
      const mapped = await fetchRecords(userId);
      setSearches(mapped);

      // Poll for missing screenshots so they appear without a page refresh
      if (mapped.some((s) => !s.screenshotUrl)) {
        startPolling();
      } else {
        stopPolling();
      }
    } catch (err) {
      setError(err instanceof Error ? err.message : 'Failed to load searches');
    } finally {
      setLoading(false);
    }
  }, [userId, fetchRecords, startPolling, stopPolling]);

  const saveSearch = useCallback(
    async (name: string, paramsOverride?: string) => {
      if (!userId) return;
      setSaving(true);
      setError(null);
      try {
        const params = paramsOverride ?? window.location.search.replace(/^\?/, '');

        // Create record immediately without screenshot
        const formData = new FormData();
        formData.append('user', userId);
        formData.append('name', name);
        formData.append('params', params);

        const record = await pb.collection('saved_searches').create(formData);
        trackEvent('Search Save');
        await fetchSearches();

        // Capture screenshot in background and attach it to the record
        const screenshotParams = new URLSearchParams(params);
        const screenshotUrl = apiUrl('screenshot', screenshotParams);
        fetch(screenshotUrl, authHeaders())
          .then((res) => {
            if (!res.ok) throw new Error(`Screenshot ${res.status}`);
            return res.blob();
          })
          .then((blob) => {
            const patch = new FormData();
            patch.append('screenshot', blob, 'screenshot.jpg');
            return pb.collection('saved_searches').update(record.id, patch);
          })
          .then(() => fetchSearches())
          .catch((err) => {
            console.warn('Background screenshot failed:', err);
          });
      } catch (err) {
        const msg = err instanceof Error ? err.message : 'Failed to save search';
        setError(msg);
        throw err;
      } finally {
        setSaving(false);
      }
    },
    [userId, fetchSearches]
  );

  const deleteSearch = useCallback(async (id: string) => {
    setError(null);
    try {
      await pb.collection('saved_searches').delete(id);
      trackEvent('Search Delete');
      setSearches((prev) => prev.filter((s) => s.id !== id));
    } catch (err) {
      setError(err instanceof Error ? err.message : 'Failed to delete search');
    }
  }, []);

  const updateSearchNotes = useCallback(async (id: string, notes: string) => {
    try {
      await pb.collection('saved_searches').update(id, { notes });
      setSearches((prev) => prev.map((s) => (s.id === id ? { ...s, notes } : s)));
    } catch (err) {
      setError(err instanceof Error ? err.message : 'Failed to update notes');
    }
  }, []);

  const updateSearchName = useCallback(async (id: string, name: string) => {
    try {
      await pb.collection('saved_searches').update(id, { name });
      setSearches((prev) => prev.map((s) => (s.id === id ? { ...s, name } : s)));
    } catch (err) {
      setError(err instanceof Error ? err.message : 'Failed to update name');
    }
  }, []);

  const updateSearchParams = useCallback(
    async (id: string, params: string) => {
      if (!userId) return;
      setSaving(true);
      setError(null);
      try {
        const record = await pb.collection('saved_searches').update(id, { params });
        trackEvent('Search Update');
        setSearches((prev) =>
          prev.map((s) => (s.id === id ? { ...s, params, screenshotUrl: '' } : s))
        );

        // Refresh screenshot in the background
        const screenshotParams = new URLSearchParams(params);
        const screenshotUrl = apiUrl('screenshot', screenshotParams);
        fetch(screenshotUrl, authHeaders())
          .then((res) => {
            if (!res.ok) throw new Error(`Screenshot ${res.status}`);
            return res.blob();
          })
          .then((blob) => {
            const patch = new FormData();
            patch.append('screenshot', blob, 'screenshot.jpg');
            return pb.collection('saved_searches').update(record.id, patch);
          })
          .then(() => fetchSearches())
          .catch((err) => {
            console.warn('Background screenshot failed:', err);
          });
      } catch (err) {
        const msg = err instanceof Error ? err.message : 'Failed to update search';
        setError(msg);
        throw err;
      } finally {
        setSaving(false);
      }
    },
    [userId, fetchSearches]
  );

  return {
    searches,
    loading,
    saving,
    error,
    fetchSearches,
    saveSearch,
    deleteSearch,
    updateSearchNotes,
    updateSearchName,
    updateSearchParams,
  };
}
