import React, {
  useState,
  useEffect,
  useLayoutEffect,
  useRef,
  useCallback,
} from "react";
import { messageAPI } from "../services/api";
import { getSignedImageUrl, uploadImageWithVariants } from "../services/uploadService";
import { User, Message } from "../types";
import { useAuth } from "../contexts/AuthContext";
import { useSocket } from "../contexts/SocketContext";
import TypingIndicator from "./TypingIndicator";
import Avatar from "./Avatar";

interface ChatWindowProps {
  show: boolean;
  onClose: () => void;
  recipient: User;
  onMessagesRead?: (partnerId: string, count: number) => void;
}

const ChatWindow: React.FC<ChatWindowProps> = ({
  show,
  onClose,
  recipient,
  onMessagesRead,
}) => {
  const { user: currentUser } = useAuth();
  const {
    isConnected,
    joinConversation,
    leaveConversation,
    onNewMessage,
    onUserTyping,
    emitTypingStart,
    emitTypingStop,
  } = useSocket();
  const [messages, setMessages] = useState<Message[]>([]);
  const [newMessage, setNewMessage] = useState("");
  const [sending, setSending] = useState(false);
  const [loading, setLoading] = useState(true);
  const [isRecipientTyping, setIsRecipientTyping] = useState(false);
  const [initialUnreadMessageIds, setInitialUnreadMessageIds] = useState<
    Set<string>
  >(new Set());
  const [isAtBottom, setIsAtBottom] = useState(true);
  const [hasScrolledToUnread, setHasScrolledToUnread] = useState(false);
  const [selectedImage, setSelectedImage] = useState<File | null>(null);
  const [imagePreview, setImagePreview] = useState<string | null>(null);
  const [imageUrls, setImageUrls] = useState<Map<string, string>>(new Map());
  const messagesEndRef = useRef<HTMLDivElement>(null);
  const messagesContainerRef = useRef<HTMLDivElement>(null);
  const messageRefs = useRef<Map<string, HTMLDivElement>>(new Map());
  const typingTimeoutRef = useRef<NodeJS.Timeout | null>(null);
  const inputRef = useRef<HTMLInputElement>(null);
  const fileInputRef = useRef<HTMLInputElement>(null);

  useEffect(() => {
    if (show) {
      setHasScrolledToUnread(false); // Reset flag when opening chat
      fetchMessages();

      // Join conversation room when chat opens
      if (isConnected) {
        joinConversation(recipient.id);
      }
    }

    return () => {
      // Leave conversation room when chat closes
      if (isConnected) {
        leaveConversation(recipient.id);
      }
    };
  }, [show, recipient.id, isConnected]);

  // Create a stable callback for handling new messages
  const handleNewMessage = useCallback(
    async (message: Message) => {
      // Only add messages that are part of this conversation
      if (
        (message.senderId === recipient.id &&
          message.receiverId === currentUser?.id) ||
        (message.senderId === currentUser?.id &&
          message.receiverId === recipient.id)
      ) {
        setMessages((prevMessages) => {
          // Check if message already exists (avoid duplicates)
          if (prevMessages.some((m) => m.id === message.id)) {
            return prevMessages;
          }
          return [...prevMessages, message];
        });

        // Mark incoming messages from recipient as read
        if (
          message.senderId === recipient.id &&
          message.receiverId === currentUser?.id &&
          !message.isRead
        ) {
          try {
            await messageAPI.markAsRead(message.id);

            // Notify parent component that message was marked read
            if (onMessagesRead) {
              onMessagesRead(recipient.id, 1);
            }
          } catch (error) {
            console.error(
              `Failed to mark message ${message.id} as read:`,
              error
            );
          }
        }
      }
    },
    [recipient.id, currentUser?.id, onMessagesRead]
  );

  // Listen for new messages in real-time
  useEffect(() => {
    if (!isConnected || !show) {
      return;
    }

    const cleanup = onNewMessage(handleNewMessage);

    return () => {
      cleanup();
    };
  }, [isConnected, show, onNewMessage, handleNewMessage]);

  // Listen for typing indicators
  useEffect(() => {
    if (!isConnected || !show) return;

    const cleanup = onUserTyping((data) => {
      // Only show typing indicator for the current recipient
      if (data.userId === recipient.id) {
        setIsRecipientTyping(data.isTyping);

        // Auto-hide typing indicator after 3 seconds of no activity
        if (data.isTyping) {
          if (typingTimeoutRef.current) {
            clearTimeout(typingTimeoutRef.current);
          }
          typingTimeoutRef.current = setTimeout(() => {
            setIsRecipientTyping(false);
          }, 3000);
        }
      }
    });

    return () => {
      cleanup();
      if (typingTimeoutRef.current) {
        clearTimeout(typingTimeoutRef.current);
      }
    };
  }, [isConnected, show, recipient.id, onUserTyping]);

  // Initial scroll to unread messages (runs synchronously after DOM updates, refs are ready)
  useLayoutEffect(() => {
    if (!loading && !hasScrolledToUnread && messages.length > 0) {
      if (initialUnreadMessageIds.size > 0) {
        // Find the oldest unread message
        const oldestUnread = messages.find((m) =>
          initialUnreadMessageIds.has(m.id)
        );

        if (oldestUnread && messageRefs.current.has(oldestUnread.id)) {
          messageRefs.current.get(oldestUnread.id)?.scrollIntoView({
            behavior: "smooth",
            block: "start",
          });
        } else {
          scrollToBottom();
        }
      } else {
        // No unread messages, scroll to bottom
        scrollToBottom();
      }
      setHasScrolledToUnread(true);
    }
  }, [loading, hasScrolledToUnread, messages, initialUnreadMessageIds]);

  // Auto-scroll for new messages (only if user is at bottom)
  useEffect(() => {
    if (isAtBottom && hasScrolledToUnread) {
      scrollToBottom();
    }
  }, [messages, isRecipientTyping, isAtBottom, hasScrolledToUnread]);

  // Pre-fetch signed URLs for private message images
  useEffect(() => {
    const fetchImageUrls = async () => {
      const messagesWithImages = messages.filter(
        (m) => m.imageFilename && !imageUrls.has(m.imageFilename)
      );

      if (messagesWithImages.length === 0) return;

      const newUrls = new Map(imageUrls);
      await Promise.all(
        messagesWithImages.map(async (m) => {
          // Use thumbnail size for chat previews
          const url = await getSignedImageUrl(m.imageFilename!, 'thumbnail');
          newUrls.set(m.imageFilename!, url);
        })
      );

      setImageUrls(newUrls);
    };

    fetchImageUrls();
  }, [messages]);

  const fetchMessages = async () => {
    try {
      // Fetch all messages between current user and recipient
      const [sentRes, receivedRes] = await Promise.all([
        messageAPI.getSentMessages(),
        messageAPI.getMessages(),
      ]);

      const sentToRecipient = sentRes.data.filter(
        (msg: Message) => msg.receiverId === recipient.id
      );
      const receivedFromRecipient = receivedRes.data.filter(
        (msg: Message) => msg.senderId === recipient.id
      );

      // Combine and sort by date
      const allMessages = [...sentToRecipient, ...receivedFromRecipient].sort(
        (a, b) =>
          new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime()
      );

      setMessages(allMessages);

      // Mark all unread messages from recipient as read
      const unreadMessages = receivedFromRecipient.filter(
        (msg: Message) => !msg.isRead
      );
      if (unreadMessages.length > 0) {
        // Save unread message IDs for scrolling purposes
        setInitialUnreadMessageIds(
          new Set(unreadMessages.map((m: Message) => m.id))
        );

        // Mark each message as read
        const markReadPromises = unreadMessages.map((message: Message) =>
          messageAPI.markAsRead(message.id).catch((err: any) => {
            console.error(`Failed to mark message ${message.id} as read:`, err);
          })
        );

        await Promise.all(markReadPromises);

        // Notify parent component that messages were marked read
        if (onMessagesRead) {
          onMessagesRead(recipient.id, unreadMessages.length);
        }
      }
    } catch (error) {
      console.error("Failed to fetch messages:", error);
    } finally {
      setLoading(false);
    }
  };

  const scrollToBottom = () => {
    messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
  };

  const setMessageRef = useCallback(
    (id: string) => (el: HTMLDivElement | null) => {
      if (el) {
        messageRefs.current.set(id, el);
      } else {
        messageRefs.current.delete(id);
      }
    },
    []
  );

  const handleScroll = () => {
    if (!messagesContainerRef.current) return;

    const { scrollTop, scrollHeight, clientHeight } =
      messagesContainerRef.current;
    const isBottom = scrollHeight - scrollTop - clientHeight < 50; // 50px threshold
    setIsAtBottom(isBottom);
  };

  // Handle typing indicators with debouncing
  const handleTyping = useCallback(() => {
    if (!isConnected) return;

    // Emit typing start
    emitTypingStart(recipient.id);

    // Clear existing timeout
    if (typingTimeoutRef.current) {
      clearTimeout(typingTimeoutRef.current);
    }

    // Set timeout to emit typing stop after 2 seconds of inactivity
    typingTimeoutRef.current = setTimeout(() => {
      emitTypingStop(recipient.id);
    }, 2000);
  }, [isConnected, recipient.id, emitTypingStart, emitTypingStop]);

  const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setNewMessage(e.target.value);
    handleTyping();
  };

  const handleImageSelect = (e: React.ChangeEvent<HTMLInputElement>) => {
    const file = e.target.files?.[0];
    if (file) {
      // Validate file type
      if (!file.type.startsWith("image/")) {
        alert("Please select an image file");
        return;
      }

      // Validate file size (5MB)
      if (file.size > 5 * 1024 * 1024) {
        alert("Image size must be less than 5MB");
        return;
      }

      setSelectedImage(file);

      // Create preview URL
      const reader = new FileReader();
      reader.onloadend = () => {
        setImagePreview(reader.result as string);
      };
      reader.readAsDataURL(file);
    }
  };

  const handleRemoveImage = () => {
    setSelectedImage(null);
    setImagePreview(null);
    if (fileInputRef.current) {
      fileInputRef.current.value = "";
    }
  };

  const handleImageButtonClick = () => {
    fileInputRef.current?.click();
  };

  const handleSend = async (e: React.FormEvent) => {
    e.preventDefault();
    if (!newMessage.trim() && !selectedImage) return;

    // Stop typing indicator
    if (isConnected) {
      emitTypingStop(recipient.id);
    }

    setSending(true);
    const messageContent = newMessage;
    const imageToSend = selectedImage;
    setNewMessage(""); // Clear input immediately for better UX
    setSelectedImage(null);
    setImagePreview(null);
    if (fileInputRef.current) {
      fileInputRef.current.value = "";
    }

    try {
      // Upload image to S3 first if present (with resizing)
      let imageFilename: string | undefined;
      if (imageToSend) {
        const { baseKey } = await uploadImageWithVariants("message", imageToSend);
        imageFilename = baseKey;
      }

      const response = await messageAPI.sendMessage({
        receiverId: recipient.id,
        content: messageContent || "",
        imageFilename,
      });

      // Add message to sender's chat immediately for instant feedback
      // Socket will handle updating the receiver's chat
      setMessages((prevMessages) => {
        // Avoid duplicates
        if (prevMessages.some((m) => m.id === response.data.id)) {
          return prevMessages;
        }
        return [...prevMessages, response.data];
      });
    } catch (error) {
      console.error("Failed to send message:", error);
      setNewMessage(messageContent); // Restore message on error
      setSelectedImage(imageToSend);
      if (imageToSend) {
        const reader = new FileReader();
        reader.onloadend = () => {
          setImagePreview(reader.result as string);
        };
        reader.readAsDataURL(imageToSend);
      }
    } finally {
      setSending(false);
      // Defer focus until after all DOM updates and scroll operations complete
      requestAnimationFrame(() => {
        requestAnimationFrame(() => {
          inputRef.current?.focus();
        });
      });
    }
  };

  const formatTime = (dateString: string) => {
    const date = new Date(dateString);
    return date.toLocaleTimeString("en-US", {
      hour: "numeric",
      minute: "2-digit",
      hour12: true,
    });
  };

  const formatDate = (dateString: string) => {
    const date = new Date(dateString);
    const today = new Date();

    if (date.toDateString() === today.toDateString()) {
      return "Today";
    }

    const yesterday = new Date(today);
    yesterday.setDate(yesterday.getDate() - 1);
    if (date.toDateString() === yesterday.toDateString()) {
      return "Yesterday";
    }

    return date.toLocaleDateString("en-US", {
      month: "short",
      day: "numeric",
      year: date.getFullYear() !== today.getFullYear() ? "numeric" : undefined,
    });
  };

  if (!show) return null;

  return (
    <div
      className="position-fixed bottom-0 end-0 m-3 shadow-lg d-flex flex-column"
      style={{
        width: "350px",
        height: "500px",
        maxHeight: "calc(100vh - 100px)",
        zIndex: 1050,
        borderRadius: "12px",
        overflow: "hidden",
        backgroundColor: "white",
      }}
    >
      {/* Header */}
      <div className="bg-primary text-white p-3 d-flex align-items-center justify-content-between flex-shrink-0">
        <div className="d-flex align-items-center">
          <Avatar user={recipient} size="md" className="me-2" />
          <div>
            <h6 className="mb-0">
              {recipient.firstName} {recipient.lastName}
            </h6>
          </div>
        </div>
        <button
          type="button"
          className="btn-close btn-close-white"
          onClick={onClose}
        ></button>
      </div>

      {/* Messages Area */}
      <div
        ref={messagesContainerRef}
        onScroll={handleScroll}
        className="p-3 overflow-auto flex-grow-1"
        style={{
          backgroundColor: "#f8f9fa",
          minHeight: 0,
        }}
      >
        {loading ? (
          <div className="text-center py-5">
            <div className="spinner-border text-primary" role="status">
              <span className="visually-hidden">Loading...</span>
            </div>
          </div>
        ) : messages.length === 0 ? (
          <div className="text-center py-5">
            <i
              className="bi bi-chat-dots"
              style={{ fontSize: "3rem", color: "#dee2e6" }}
            ></i>
            <p className="text-muted mt-2">
              Start a conversation with {recipient.firstName}
            </p>
          </div>
        ) : (
          <>
            {messages.map((message, index) => {
              const isCurrentUser = message.senderId === currentUser?.id;
              const showDate =
                index === 0 ||
                formatDate(message.createdAt) !==
                  formatDate(messages[index - 1].createdAt);

              return (
                <div key={message.id} ref={setMessageRef(message.id)}>
                  {showDate && (
                    <div className="text-center my-3">
                      <small className="text-muted bg-white px-2 py-1 rounded">
                        {formatDate(message.createdAt)}
                      </small>
                    </div>
                  )}
                  <div
                    className={`d-flex mb-2 ${
                      isCurrentUser ? "justify-content-end" : ""
                    }`}
                  >
                    <div
                      className={`px-3 py-2 rounded-3 ${
                        isCurrentUser
                          ? "bg-primary text-white"
                          : "bg-white border"
                      }`}
                      style={{
                        maxWidth: "75%",
                        wordBreak: "break-word",
                      }}
                    >
                      {message.imageFilename &&
                        imageUrls.has(message.imageFilename) && (
                          <div className="mb-2">
                            <img
                              src={imageUrls.get(message.imageFilename)}
                              alt="Shared image"
                              style={{
                                width: "100%",
                                borderRadius: "8px",
                                cursor: "pointer",
                                maxHeight: "300px",
                                objectFit: "cover",
                              }}
                              onClick={() =>
                                window.open(
                                  imageUrls.get(message.imageFilename!),
                                  "_blank"
                                )
                              }
                            />
                          </div>
                        )}
                      {message.content?.trim() && (
                        <p className="mb-1" style={{ fontSize: "0.95rem" }}>
                          {message.content}
                        </p>
                      )}
                      <small
                        className={isCurrentUser ? "opacity-75" : "text-muted"}
                        style={{ fontSize: "0.75rem" }}
                      >
                        {formatTime(message.createdAt)}
                      </small>
                    </div>
                  </div>
                </div>
              );
            })}
            {/* Typing indicator */}
            {isRecipientTyping && (
              <TypingIndicator
                firstName={recipient.firstName}
                isVisible={isRecipientTyping}
              />
            )}
            <div ref={messagesEndRef} />
          </>
        )}
      </div>

      {/* Input Area */}
      <form onSubmit={handleSend} className="border-top p-3 flex-shrink-0">
        {/* Image Preview */}
        {imagePreview && (
          <div className="mb-2 position-relative d-inline-block">
            <img
              src={imagePreview}
              alt="Preview"
              style={{
                maxWidth: "150px",
                maxHeight: "150px",
                borderRadius: "8px",
                objectFit: "cover",
              }}
            />
            <button
              type="button"
              className="btn btn-sm btn-danger position-absolute top-0 end-0 m-1 rounded-circle"
              onClick={handleRemoveImage}
              style={{
                width: "24px",
                height: "24px",
                padding: "0",
                fontSize: "0.7rem",
              }}
            >
              <i className="bi bi-x"></i>
            </button>
          </div>
        )}
        <div className="input-group">
          <input
            ref={fileInputRef}
            type="file"
            accept="image/*"
            onChange={handleImageSelect}
            style={{ display: "none" }}
          />
          <button
            type="button"
            className="btn btn-outline-secondary"
            onClick={handleImageButtonClick}
            disabled={sending}
            title="Attach image"
          >
            <i className="bi bi-image"></i>
          </button>
          <input
            ref={inputRef}
            type="text"
            className="form-control"
            placeholder="Type a message..."
            value={newMessage}
            onChange={handleInputChange}
            disabled={sending}
          />
          <button
            className="btn btn-primary"
            type="submit"
            disabled={sending || (!newMessage.trim() && !selectedImage)}
          >
            {sending ? (
              <span
                className="spinner-border spinner-border-sm"
                role="status"
                aria-hidden="true"
              ></span>
            ) : (
              <i className="bi bi-send-fill"></i>
            )}
          </button>
        </div>
      </form>
    </div>
  );
};

export default ChatWindow;
