import { createSignal, createEffect, onCleanup, Show } from "solid-js";
import type {
  Comment,
  CommentReaction,
  CommentSectionWrapperProps,
  WebSocketBroadcast,
  BackupResponse,
  UserPublicData,
  ReactionType,
  DeletionType
} from "~/types/comment";
import { getSQLFormattedDate } from "~/lib/comment-utils";
import { api } from "~/lib/api";
import CommentSection from "./CommentSection";
import CommentDeletionPrompt from "./CommentDeletionPrompt";
import EditCommentModal from "./EditCommentModal";
import { env } from "~/env/client";

const MAX_RETRIES = 12;
const RETRY_INTERVAL = 5000;

export default function CommentSectionWrapper(
  props: CommentSectionWrapperProps
) {
  // State signals
  const [allComments, setAllComments] = createSignal<Comment[]>(
    props.allComments
  );
  const [topLevelComments, setTopLevelComments] = createSignal<Comment[]>(
    props.topLevelComments
  );
  const [currentReactionMap, setCurrentReactionMap] = createSignal<
    Map<number, CommentReaction[]>
  >(props.reactionMap);
  const [commentSubmitLoading, setCommentSubmitLoading] =
    createSignal<boolean>(false);
  const [commentDeletionLoading, setCommentDeletionLoading] =
    createSignal<boolean>(false);
  const [editCommentLoading, setCommentEditLoading] =
    createSignal<boolean>(false);
  const [showingCommentEdit, setShowingCommentEdit] =
    createSignal<boolean>(false);
  const [showingDeletionPrompt, setShowingDeletionPrompt] =
    createSignal<boolean>(false);
  const [commentIDForModification, setCommentIDForModification] =
    createSignal<number>(-1);
  const [commenterForModification, setCommenterForModification] =
    createSignal<string>("");
  const [commenterImageForModification, setCommenterImageForModification] =
    createSignal<string | undefined>(undefined);
  const [commenterEmailForModification, setCommenterEmailForModification] =
    createSignal<string | undefined>(undefined);
  const [
    commenterDisplayNameForModification,
    setCommenterDisplayNameForModification
  ] = createSignal<string | undefined>(undefined);
  const [commentBodyForModification, setCommentBodyForModification] =
    createSignal<string>("");

  // Non-reactive refs (store without triggering reactivity)
  let userCommentMap: Map<UserPublicData, number[]> = props.userCommentMap;
  let deletePromptRef: HTMLDivElement | undefined;
  let modificationPromptRef: HTMLDivElement | undefined;
  let retryCount = 0;
  let socket: WebSocket | undefined;

  // WebSocket connection effect
  createEffect(() => {
    const connect = () => {
      if (socket) return;
      if (retryCount > MAX_RETRIES) {
        console.error("Max retries exceeded!");
        return;
      }

      const websocketUrl = env.VITE_WEBSOCKET;
      if (!websocketUrl) {
        console.error("VITE_WEBSOCKET environment variable not set");
        return;
      }

      const newSocket = new WebSocket(websocketUrl);

      newSocket.onopen = () => {
        updateChannel();
        retryCount = 0;
      };

      newSocket.onclose = () => {
        retryCount += 1;
        socket = undefined;
        setTimeout(connect, RETRY_INTERVAL);
      };

      newSocket.onmessage = (messageEvent) => {
        try {
          const parsed = JSON.parse(messageEvent.data) as WebSocketBroadcast;
          switch (parsed.action) {
            case "commentCreationBroadcast":
              createCommentHandler(parsed);
              break;
            case "commentUpdateBroadcast":
              editCommentHandler(parsed);
              break;
            case "commentDeletionBroadcast":
              deleteCommentHandler(parsed);
              break;
            case "commentReactionBroadcast":
              commentReactionHandler(parsed);
              break;
            default:
              break;
          }
        } catch (e) {
          console.error(e);
        }
      };

      socket = newSocket;
    };

    connect();

    // Cleanup on unmount
    onCleanup(() => {
      if (socket?.readyState === WebSocket.OPEN) {
        socket.close();
        socket = undefined;
      }
    });
  });

  // Helper functions
  const updateChannel = () => {
    if (!socket || socket.readyState !== WebSocket.OPEN) {
      return;
    }

    if (!props.currentUserID || !props.id) {
      console.warn("Cannot update channel: missing userID or postID");
      return;
    }

    try {
      socket.send(
        JSON.stringify({
          action: "channelUpdate",
          postType: "blog",
          postID: props.id,
          invoker_id: props.currentUserID
        })
      );
    } catch (error) {
      console.error("Error sending channel update:", error);
    }
  };

  // Comment creation
  const newComment = async (commentBody: string, parentCommentID?: number) => {
    setCommentSubmitLoading(true);

    if (!props.currentUserID) {
      console.warn("Cannot create comment: user not authenticated");
      setCommentSubmitLoading(false);
      return;
    }

    if (commentBody && socket) {
      try {
        socket.send(
          JSON.stringify({
            action: "commentCreation",
            commentBody: commentBody,
            postType: "blog",
            postID: props.id,
            parentCommentID: parentCommentID,
            invokerID: props.currentUserID
          })
        );
      } catch (error) {
        console.error("Error sending comment creation:", error);
        // Fallback to HTTP API on WebSocket error
        await fallbackCommentCreation(commentBody, parentCommentID);
      }
    } else {
      // Fallback to HTTP API if WebSocket unavailable
      await fallbackCommentCreation(commentBody, parentCommentID);
    }
  };

  const fallbackCommentCreation = async (
    commentBody: string,
    parentCommentID?: number
  ) => {
    try {
      const domain = env.VITE_DOMAIN;
      const res = await fetch(
        `${domain}/api/database/comments/create/blog/${props.id}`,
        {
          method: "POST",
          body: JSON.stringify({
            body: commentBody,
            parentCommentID: parentCommentID,
            commenterID: props.currentUserID
          })
        }
      );
      if (res.status === 201) {
        const id = (await res.json()).data;
        createCommentHandler({
          commentBody: commentBody,
          commentID: id,
          commenterID: props.currentUserID,
          commentParent: parentCommentID
        });
      }
    } catch (error) {
      console.error("Error in fallback comment creation:", error);
      setCommentSubmitLoading(false);
    }
  };

  const createCommentHandler = async (
    data: WebSocketBroadcast | BackupResponse
  ) => {
    const body = data.commentBody;
    const commenterID = data.commenterID;
    const parentCommentID = data.commentParent;
    const id = data.commentID;

    if (body && commenterID && parentCommentID !== undefined && id) {
      const domain = env.VITE_DOMAIN;
      const res = await fetch(
        `${domain}/api/database/user/public-data/${commenterID}`
      );
      const userData = (await res.json()) as UserPublicData;

      const comment_date = getSQLFormattedDate();
      const newComment: Comment = {
        id: id,
        body: body,
        post_id: props.id,
        parent_comment_id: parentCommentID,
        commenter_id: commenterID,
        edited: false,
        date: comment_date
      };

      if (parentCommentID === -1 || parentCommentID === null) {
        setTopLevelComments((prevComments) => [
          ...(prevComments || []),
          newComment
        ]);
      }
      setAllComments((prevComments) => [...(prevComments || []), newComment]);

      // Update user comment map
      const existingIDs = Array.from(userCommentMap.entries()).find(
        ([key, _]) =>
          key.email === userData.email &&
          key.display_name === userData.display_name &&
          key.image === userData.image
      );

      if (existingIDs) {
        const [key, ids] = existingIDs;
        userCommentMap.set(key, [...ids, id]);
      } else {
        userCommentMap.set(userData, [id]);
      }
    }
    setCommentSubmitLoading(false);
  };

  // Comment updating
  const editComment = async (body: string, comment_id: number) => {
    setCommentEditLoading(true);

    if (!props.currentUserID) {
      console.warn("Cannot edit comment: user not authenticated");
      setCommentEditLoading(false);
      return;
    }

    if (socket) {
      try {
        socket.send(
          JSON.stringify({
            action: "commentUpdate",
            commentBody: body,
            postType: "blog",
            postID: props.id,
            commentID: comment_id,
            invokerID: props.currentUserID
          })
        );
      } catch (error) {
        console.error("Error sending comment update:", error);
        setCommentEditLoading(false);
      }
    }
  };

  const editCommentHandler = (data: WebSocketBroadcast) => {
    setAllComments((prev) =>
      prev.map((comment) => {
        if (comment.id === data.commentID) {
          return {
            ...comment,
            body: data.commentBody!,
            edited: true
          };
        }
        return comment;
      })
    );
    setTopLevelComments((prev) =>
      prev.map((comment) => {
        if (comment.id === data.commentID) {
          return {
            ...comment,
            body: data.commentBody!,
            edited: true
          };
        }
        return comment;
      })
    );
    setCommentEditLoading(false);
    setTimeout(() => {
      setShowingCommentEdit(false);
      clearModificationPrompt();
    }, 300);
  };

  // Comment deletion
  const deleteComment = async (
    commentID: number,
    commenterID: string,
    deletionType: DeletionType
  ) => {
    console.log("[deleteComment] Starting deletion:", {
      commentID,
      commenterID,
      deletionType,
      currentUserID: props.currentUserID,
      socketState: socket?.readyState
    });

    setCommentDeletionLoading(true);

    if (!props.currentUserID) {
      console.warn(
        "[deleteComment] Cannot delete comment: user not authenticated"
      );
      setCommentDeletionLoading(false);
      return;
    }

    if (socket && socket.readyState === WebSocket.OPEN) {
      console.log("[deleteComment] Using WebSocket");
      try {
        socket.send(
          JSON.stringify({
            action: "commentDeletion",
            deleteType: deletionType,
            commentID: commentID,
            invokerID: props.currentUserID,
            postType: "blog",
            postID: props.id
          })
        );
      } catch (error) {
        console.error(
          "[deleteComment] WebSocket error, falling back to HTTP:",
          error
        );
        // Fallback to HTTP API on WebSocket error
        await fallbackCommentDeletion(commentID, commenterID, deletionType);
      }
    } else {
      console.log(
        "[deleteComment] WebSocket not available, using HTTP fallback"
      );
      // Fallback to HTTP API if WebSocket unavailable
      await fallbackCommentDeletion(commentID, commenterID, deletionType);
    }
  };

  const fallbackCommentDeletion = async (
    commentID: number,
    commenterID: string,
    deletionType: DeletionType
  ) => {
    console.log("[fallbackCommentDeletion] Calling tRPC endpoint:", {
      commentID,
      commenterID,
      deletionType
    });

    try {
      const result = await api.database.deleteComment.mutate({
        commentID,
        commenterID,
        deletionType
      });

      console.log("[fallbackCommentDeletion] Success:", result);

      // Handle the deletion response
      deleteCommentHandler({
        action: "commentDeletionBroadcast",
        commentID: commentID,
        commentBody: result.commentBody || undefined,
        commenterID: commenterID,
        deletionType: deletionType
      });
    } catch (error) {
      console.error("[fallbackCommentDeletion] Error:", error);
      setCommentDeletionLoading(false);
    }
  };

  const deleteCommentHandler = (data: WebSocketBroadcast) => {
    if (data.commentBody) {
      // Soft delete (replace body with deletion message)
      setAllComments((prev) =>
        prev.map((comment) => {
          if (comment.id === data.commentID) {
            return {
              ...comment,
              body: data.commentBody!,
              commenter_id: "",
              edited: false
            };
          }
          return comment;
        })
      );

      setTopLevelComments((prev) =>
        prev.map((comment) => {
          if (comment.id === data.commentID) {
            return {
              ...comment,
              body: data.commentBody!,
              commenter_id: "",
              edited: false
            };
          }
          return comment;
        })
      );
    } else {
      // Hard delete (remove from list)
      setAllComments((prev) =>
        prev.filter((comment) => comment.id !== data.commentID)
      );
      setTopLevelComments((prev) =>
        prev.filter((comment) => comment.id !== data.commentID)
      );
    }
    setCommentDeletionLoading(false);
    setTimeout(() => {
      clearModificationPrompt();
      setShowingDeletionPrompt(false);
    }, 300);
  };

  // Deletion/edit prompt toggle
  const toggleModification = (
    commentID: number,
    commenterID: string,
    commentBody: string,
    modificationType: "delete" | "edit",
    commenterImage?: string,
    commenterEmail?: string,
    commenterDisplayName?: string
  ) => {
    if (commentID === commentIDForModification()) {
      if (modificationType === "delete") {
        setShowingDeletionPrompt(false);
      } else {
        setShowingCommentEdit(false);
      }
      clearModificationPrompt();
    } else {
      if (modificationType === "delete") {
        setShowingDeletionPrompt(true);
      } else {
        setShowingCommentEdit(true);
      }
      setCommentIDForModification(commentID);
      setCommenterForModification(commenterID);
      setCommenterImageForModification(commenterImage);
      setCommenterEmailForModification(commenterEmail);
      setCommenterDisplayNameForModification(commenterDisplayName);
      setCommentBodyForModification(commentBody);
    }
  };

  const clearModificationPrompt = () => {
    setCommentIDForModification(-1);
    setCommenterForModification("");
    setCommenterImageForModification(undefined);
    setCommenterEmailForModification(undefined);
    setCommenterDisplayNameForModification(undefined);
    setCommentBodyForModification("");
  };

  // Reaction handling
  const commentReaction = (reactionType: ReactionType, commentID: number) => {
    if (!props.currentUserID) {
      console.warn("Cannot react to comment: user not authenticated");
      return;
    }

    if (socket) {
      try {
        socket.send(
          JSON.stringify({
            action: "commentReaction",
            postType: "blog",
            postID: props.id,
            commentID: commentID,
            invokerID: props.currentUserID,
            reactionType: reactionType
          })
        );
      } catch (error) {
        console.error("Error sending comment reaction:", error);
      }
    }
  };

  const commentReactionHandler = (data: any) => {
    switch (data.endEffect) {
      case "creation":
        if (data.commentID && data.reactionType && data.reactingUserID) {
          const newReaction: CommentReaction = {
            id: -1,
            type: data.reactionType,
            comment_id: data.commentID,
            user_id: data.reactingUserID,
            date: getSQLFormattedDate()
          };
          setCurrentReactionMap((prevMap) => {
            const entries = [
              ...(prevMap.get(data.commentID!) || []),
              newReaction
            ];
            return new Map([...prevMap, [data.commentID!, entries]]);
          });
        }
        break;

      case "deletion":
        if (data.commentID) {
          setCurrentReactionMap((prevMap) => {
            const entries = (prevMap.get(data.commentID!) || []).filter(
              (reaction) =>
                reaction.user_id !== data.reactingUserID ||
                reaction.type !== data.reactionType
            );
            return new Map([...prevMap, [data.commentID!, entries]]);
          });
        }
        break;

      case "inversion":
        // Only applies to upvotes/downvotes (vote inversion)
        if (
          data.commentID &&
          data.reactingUserID &&
          data.reactionType &&
          (data.reactionType === "upVote" || data.reactionType === "downVote")
        ) {
          setCurrentReactionMap((prevMap) => {
            let entries = (prevMap.get(data.commentID!) || []).filter(
              (reaction) =>
                reaction.user_id !== data.reactingUserID ||
                reaction.type !==
                  (data.reactionType === "upVote" ? "downVote" : "upVote")
            );
            const newReaction: CommentReaction = {
              id: -1,
              type: data.reactionType!,
              comment_id: data.commentID!,
              user_id: data.reactingUserID!,
              date: getSQLFormattedDate()
            };
            entries = entries.concat(newReaction);
            return new Map([...prevMap, [data.commentID!, entries]]);
          });
        }
        break;

      default:
        console.log("endEffect value unknown");
    }
  };

  // Click outside handlers (SolidJS version)
  createEffect(() => {
    const handleClickOutsideDelete = (e: MouseEvent) => {
      if (
        deletePromptRef &&
        !deletePromptRef.contains(e.target as Node) &&
        showingDeletionPrompt()
      ) {
        setShowingDeletionPrompt(false);
      }
    };

    const handleClickOutsideEdit = (e: MouseEvent) => {
      if (
        modificationPromptRef &&
        !modificationPromptRef.contains(e.target as Node) &&
        showingCommentEdit()
      ) {
        setShowingCommentEdit(false);
      }
    };

    document.addEventListener("mousedown", handleClickOutsideDelete);
    document.addEventListener("mousedown", handleClickOutsideEdit);

    onCleanup(() => {
      document.removeEventListener("mousedown", handleClickOutsideDelete);
      document.removeEventListener("mousedown", handleClickOutsideEdit);
    });
  });

  return (
    <>
      <CommentSection
        privilegeLevel={props.privilegeLevel}
        allComments={allComments()}
        topLevelComments={topLevelComments()}
        postID={props.id}
        reactionMap={currentReactionMap()}
        currentUserID={props.currentUserID}
        userCommentMap={userCommentMap}
        newComment={newComment}
        commentSubmitLoading={commentSubmitLoading()}
        toggleModification={toggleModification}
        commentReaction={commentReaction}
      />

      <Show when={showingDeletionPrompt()}>
        <div ref={deletePromptRef}>
          <CommentDeletionPrompt
            commentID={commentIDForModification()}
            commenterID={commenterForModification()}
            commenterImage={commenterImageForModification()}
            commenterEmail={commenterEmailForModification()}
            commenterDisplayName={commenterDisplayNameForModification()}
            privilegeLevel={props.privilegeLevel}
            commentDeletionLoading={commentDeletionLoading()}
            deleteComment={deleteComment}
          />
        </div>
      </Show>

      <Show when={showingCommentEdit()}>
        <div ref={modificationPromptRef}>
          <EditCommentModal
            commentID={commentIDForModification()}
            commentBody={commentBodyForModification()}
            commenterImage={commenterImageForModification()}
            commenterEmail={commenterEmailForModification()}
            commenterDisplayName={commenterDisplayNameForModification()}
            editCommentLoading={editCommentLoading()}
            editComment={editComment}
          />
        </div>
      </Show>
    </>
  );
}
