import { createContext, useCallback, useEffect, useMemo, useRef, useState } from "react";
import { updateItemImage } from "../api/list";
import {
  deleteUploadJob,
  getAllUploadJobs,
  saveUploadJob,
} from "../lib/uploadQueueStorage";

const SUCCESS_DISMISS_DELAY_MS = 2500;
const NETWORK_TIMEOUT_MS = 90000;
export const IMAGE_UPLOAD_SUCCESS_EVENT = "upload-queue:image-upload-success";

export const UploadQueueContext = createContext({
  uploads: [],
  isOnline: true,
  enqueueImageUpload: () => "",
  retryUpload: () => {},
  discardUpload: () => {},
});

function nowTs() {
  return Date.now();
}

function createId() {
  if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
    return crypto.randomUUID();
  }
  return `upl_${nowTs()}_${Math.random().toString(16).slice(2)}`;
}

function classifyUploadError(error, isOnline) {
  if (!isOnline || error?.code === "ERR_NETWORK" || error?.code === "ECONNABORTED") {
    return "Network issue. Check your connection and retry.";
  }

  const responseMessage =
    error?.response?.data?.error?.message ||
    error?.response?.data?.message ||
    error?.message;

  return responseMessage || "Upload failed. Retry or discard.";
}

function matchesItemKey(a, b) {
  return (
    a.householdId === b.householdId &&
    a.storeId === b.storeId &&
    String(a.itemName || "").toLowerCase() === String(b.itemName || "").toLowerCase()
  );
}

export function UploadQueueProvider({ children }) {
  const [uploads, setUploads] = useState([]);
  const [isOnline, setIsOnline] = useState(
    typeof navigator === "undefined" ? true : navigator.onLine
  );

  const uploadsRef = useRef([]);
  const processingRef = useRef(false);
  const controllerByIdRef = useRef(new Map());
  const successTimerByIdRef = useRef(new Map());

  useEffect(() => {
    uploadsRef.current = uploads;
  }, [uploads]);

  const removeUpload = useCallback((uploadId) => {
    const timer = successTimerByIdRef.current.get(uploadId);
    if (timer) {
      clearTimeout(timer);
      successTimerByIdRef.current.delete(uploadId);
    }

    const controller = controllerByIdRef.current.get(uploadId);
    if (controller) {
      controller.abort();
      controllerByIdRef.current.delete(uploadId);
    }

    setUploads((prev) => prev.filter((upload) => upload.id !== uploadId));
    deleteUploadJob(uploadId).catch((error) => {
      console.error("[UploadQueue] Failed to delete upload job:", error);
    });
  }, []);

  const updateUpload = useCallback((uploadId, updater) => {
    let updatedUpload = null;

    setUploads((prev) =>
      prev.map((upload) => {
        if (upload.id !== uploadId) {
          return upload;
        }

        updatedUpload = {
          ...updater(upload),
          updatedAt: nowTs(),
        };
        return updatedUpload;
      })
    );

    if (updatedUpload) {
      saveUploadJob(updatedUpload).catch((error) => {
        console.error("[UploadQueue] Failed to persist upload job:", error);
      });
    }

    return updatedUpload;
  }, []);

  useEffect(() => {
    let isCancelled = false;

    const hydrateUploads = async () => {
      try {
        const stored = await getAllUploadJobs();
        if (isCancelled) return;

        const hydrated = [];
        for (const upload of stored) {
          if (upload.status === "discarded") {
            deleteUploadJob(upload.id).catch(() => {});
            continue;
          }

          if (upload.status === "uploading") {
            const interrupted = {
              ...upload,
              status: "failed",
              progress: 0,
              lastError: "Upload interrupted. Retry or discard.",
              updatedAt: nowTs(),
            };
            hydrated.push(interrupted);
            saveUploadJob(interrupted).catch((error) => {
              console.error("[UploadQueue] Failed to persist interrupted upload:", error);
            });
            continue;
          }

          hydrated.push(upload);
        }

        hydrated.sort((a, b) => (a.createdAt || 0) - (b.createdAt || 0));
        setUploads(hydrated);
      } catch (error) {
        console.error("[UploadQueue] Failed to hydrate uploads:", error);
      }
    };

    hydrateUploads();

    return () => {
      isCancelled = true;
    };
  }, []);

  useEffect(() => {
    const onOnline = () => setIsOnline(true);
    const onOffline = () => setIsOnline(false);

    window.addEventListener("online", onOnline);
    window.addEventListener("offline", onOffline);

    return () => {
      window.removeEventListener("online", onOnline);
      window.removeEventListener("offline", onOffline);
    };
  }, []);

  const processNextUpload = useCallback(async () => {
    if (processingRef.current || !isOnline) {
      return;
    }

    const queuedUpload = uploadsRef.current.find((upload) => upload.status === "queued");
    if (!queuedUpload) {
      return;
    }

    processingRef.current = true;
    const controller = new AbortController();
    controllerByIdRef.current.set(queuedUpload.id, controller);

    updateUpload(queuedUpload.id, (current) => ({
      ...current,
      status: "uploading",
      progress: 0,
      lastError: null,
      attemptCount: (current.attemptCount || 0) + 1,
    }));

    try {
      await updateItemImage(
        queuedUpload.householdId,
        queuedUpload.storeId,
        queuedUpload.itemName,
        queuedUpload.quantity,
        queuedUpload.fileBlob,
        {
          signal: controller.signal,
          timeoutMs: NETWORK_TIMEOUT_MS,
          onUploadProgress: (event) => {
            if (!event?.total || event.total <= 0) {
              return;
            }
            const progress = Math.max(
              1,
              Math.min(99, Math.round((event.loaded / event.total) * 100))
            );
            updateUpload(queuedUpload.id, (current) => ({
              ...current,
              status: "uploading",
              progress,
            }));
          },
        }
      );

      updateUpload(queuedUpload.id, (current) => ({
        ...current,
        status: "success",
        progress: 100,
        lastError: null,
      }));

      window.dispatchEvent(
        new CustomEvent(IMAGE_UPLOAD_SUCCESS_EVENT, {
          detail: {
            uploadId: queuedUpload.id,
            householdId: queuedUpload.householdId,
            storeId: queuedUpload.storeId,
            itemName: queuedUpload.itemName,
            localItemId: queuedUpload.localItemId || null,
          },
        })
      );
    } catch (error) {
      if (error?.code !== "ERR_CANCELED") {
        updateUpload(queuedUpload.id, (current) => ({
          ...current,
          status: "failed",
          progress: 0,
          lastError: classifyUploadError(error, isOnline),
        }));
      }
    } finally {
      controllerByIdRef.current.delete(queuedUpload.id);
      processingRef.current = false;
      setTimeout(() => {
        void processNextUpload();
      }, 0);
    }
  }, [isOnline, updateUpload]);

  useEffect(() => {
    void processNextUpload();
  }, [uploads, isOnline, processNextUpload]);

  useEffect(() => {
    const activeIds = new Set(uploads.map((upload) => upload.id));

    for (const [id, timer] of successTimerByIdRef.current.entries()) {
      if (!activeIds.has(id)) {
        clearTimeout(timer);
        successTimerByIdRef.current.delete(id);
      }
    }

    for (const upload of uploads) {
      if (upload.status !== "success") {
        continue;
      }
      if (successTimerByIdRef.current.has(upload.id)) {
        continue;
      }

      const timer = setTimeout(() => {
        removeUpload(upload.id);
      }, SUCCESS_DISMISS_DELAY_MS);
      successTimerByIdRef.current.set(upload.id, timer);
    }
  }, [uploads, removeUpload]);

  useEffect(() => {
    return () => {
      for (const timer of successTimerByIdRef.current.values()) {
        clearTimeout(timer);
      }
      successTimerByIdRef.current.clear();

      for (const controller of controllerByIdRef.current.values()) {
        controller.abort();
      }
      controllerByIdRef.current.clear();
    };
  }, []);

  const enqueueImageUpload = useCallback(
    ({
      householdId,
      storeId,
      itemName,
      quantity,
      fileBlob,
      fileName,
      fileType,
      fileSize,
      source,
      localItemId = null,
    }) => {
      const upload = {
        id: createId(),
        kind: "item_image_upload",
        status: "queued",
        householdId,
        storeId,
        itemName,
        quantity,
        fileBlob,
        fileName,
        fileType,
        fileSize,
        source,
        localItemId,
        progress: 0,
        attemptCount: 0,
        lastError: null,
        createdAt: nowTs(),
        updatedAt: nowTs(),
      };

      const toRemove = [];

      setUploads((prev) => {
        const next = [];

        for (const current of prev) {
          const isDedupCandidate =
            current.kind === "item_image_upload" &&
            ["queued", "uploading", "failed"].includes(current.status) &&
            matchesItemKey(current, upload);

          if (isDedupCandidate) {
            toRemove.push(current.id);
            continue;
          }

          next.push(current);
        }

        next.push(upload);
        return next;
      });

      for (const uploadId of toRemove) {
        const activeController = controllerByIdRef.current.get(uploadId);
        if (activeController) {
          activeController.abort();
        }
        removeUpload(uploadId);
      }

      saveUploadJob(upload).catch((error) => {
        console.error("[UploadQueue] Failed to save queued upload:", error);
      });

      return upload.id;
    },
    [removeUpload]
  );

  const retryUpload = useCallback(
    (uploadId) => {
      updateUpload(uploadId, (upload) => ({
        ...upload,
        status: "queued",
        progress: 0,
        lastError: null,
      }));
    },
    [updateUpload]
  );

  const discardUpload = useCallback(
    (uploadId) => {
      removeUpload(uploadId);
    },
    [removeUpload]
  );

  const value = useMemo(
    () => ({
      uploads,
      isOnline,
      enqueueImageUpload,
      retryUpload,
      discardUpload,
    }),
    [uploads, isOnline, enqueueImageUpload, retryUpload, discardUpload]
  );

  return <UploadQueueContext.Provider value={value}>{children}</UploadQueueContext.Provider>;
}
