import {
  ChatMessage,
  Conversation,
  CreateMessageDTO,
  CreateEventDTO,
  GetMessagesOptions,
  UpdateMessageDTO,
  ConflictingEvent,
} from "@calchat/shared";
import { ChatRepository } from "../../services/interfaces";
import { Logged } from "../../logging";
import { ChatMessageModel, ConversationModel } from "./models";

@Logged("MongoChatRepository")
export class MongoChatRepository implements ChatRepository {
  // Conversations
  async getConversationsByUser(userId: string): Promise<Conversation[]> {
    const conversations = await ConversationModel.find({ userId });
    return conversations.map((c) => c.toJSON() as unknown as Conversation);
  }

  async createConversation(userId: string): Promise<Conversation> {
    const conversation = await ConversationModel.create({
      userId,
    });
    return conversation.toJSON() as unknown as Conversation;
  }

  async getConversationById(
    conversationId: string,
  ): Promise<Conversation | null> {
    const conversation = await ConversationModel.findById(conversationId);
    return conversation
      ? (conversation.toJSON() as unknown as Conversation)
      : null;
  }

  // Messages (cursor-based pagination)
  async getMessages(
    conversationId: string,
    options?: GetMessagesOptions,
  ): Promise<ChatMessage[]> {
    const query: Record<string, unknown> = { conversationId };

    // Cursor: load messages before this ID (for "load more" scrolling up)
    if (options?.before) {
      query._id = { $lt: options.before };
    }

    // Fetch newest first, then reverse for chronological order
    // Only apply limit if explicitly specified (no default - load all messages)
    let queryBuilder = ChatMessageModel.find(query).sort({ _id: -1 });
    if (options?.limit) {
      queryBuilder = queryBuilder.limit(options.limit);
    }
    const docs = await queryBuilder;

    return docs.reverse().map((doc) => doc.toJSON() as unknown as ChatMessage);
  }

  async createMessage(
    conversationId: string,
    message: CreateMessageDTO,
  ): Promise<ChatMessage> {
    const repoMessage = await ChatMessageModel.create({
      conversationId: conversationId,
      sender: message.sender,
      content: message.content,
      proposedChanges: message.proposedChanges,
    });
    return repoMessage.toJSON() as unknown as ChatMessage;
  }

  async updateMessage(
    messageId: string,
    updates: UpdateMessageDTO,
  ): Promise<ChatMessage | null> {
    const doc = await ChatMessageModel.findByIdAndUpdate(
      messageId,
      { $set: updates },
      { new: true },
    );
    return doc ? (doc.toJSON() as unknown as ChatMessage) : null;
  }

  async updateProposalResponse(
    messageId: string,
    proposalId: string,
    respondedAction: "confirm" | "reject",
  ): Promise<ChatMessage | null> {
    const doc = await ChatMessageModel.findOneAndUpdate(
      { _id: messageId, "proposedChanges.id": proposalId },
      { $set: { "proposedChanges.$.respondedAction": respondedAction } },
      { new: true },
    );
    return doc ? (doc.toJSON() as unknown as ChatMessage) : null;
  }

  async updateProposalEvent(
    messageId: string,
    proposalId: string,
    event: CreateEventDTO,
    conflictingEvents?: ConflictingEvent[],
  ): Promise<ChatMessage | null> {
    // Always set both fields - use empty array when no conflicts
    // (MongoDB has issues combining $set and $unset on positional operator)
    const setFields: Record<string, unknown> = {
      "proposedChanges.$.event": event,
      "proposedChanges.$.conflictingEvents":
        conflictingEvents && conflictingEvents.length > 0
          ? conflictingEvents
          : [],
    };

    const doc = await ChatMessageModel.findOneAndUpdate(
      { _id: messageId, "proposedChanges.id": proposalId },
      { $set: setFields },
      { new: true },
    );
    return doc ? (doc.toJSON() as unknown as ChatMessage) : null;
  }

  async getMessageById(messageId: string): Promise<ChatMessage | null> {
    const doc = await ChatMessageModel.findById(messageId);
    return doc ? (doc.toJSON() as unknown as ChatMessage) : null;
  }
}
