import { eq, and, asc, desc, ilike, type Db, conversations, messages } from '@mosaicstack/db';

/** Maximum number of conversations returned per list query. */
const MAX_CONVERSATIONS = 200;
/** Maximum number of messages returned per conversation history query. */
const MAX_MESSAGES = 500;

export type Conversation = typeof conversations.$inferSelect;
export type NewConversation = typeof conversations.$inferInsert;
export type Message = typeof messages.$inferSelect;
export type NewMessage = typeof messages.$inferInsert;

export interface MessageSearchResult {
  messageId: string;
  conversationId: string;
  conversationTitle: string | null;
  role: 'user' | 'assistant' | 'system';
  content: string;
  createdAt: Date;
}

export function createConversationsRepo(db: Db) {
  return {
    async findAll(userId: string): Promise<Conversation[]> {
      return db
        .select()
        .from(conversations)
        .where(eq(conversations.userId, userId))
        .orderBy(desc(conversations.updatedAt))
        .limit(MAX_CONVERSATIONS);
    },

    /**
     * Find a conversation by ID, scoped to the given user.
     * Returns undefined if the conversation does not exist or belongs to a different user.
     */
    async findById(id: string, userId: string): Promise<Conversation | undefined> {
      const rows = await db
        .select()
        .from(conversations)
        .where(and(eq(conversations.id, id), eq(conversations.userId, userId)));
      return rows[0];
    },

    async create(data: NewConversation): Promise<Conversation> {
      const rows = await db.insert(conversations).values(data).returning();
      return rows[0]!;
    },

    /**
     * Update a conversation, scoped to the given user.
     * Returns undefined if the conversation does not exist or belongs to a different user.
     */
    async update(
      id: string,
      userId: string,
      data: Partial<NewConversation>,
    ): Promise<Conversation | undefined> {
      const rows = await db
        .update(conversations)
        .set({ ...data, updatedAt: new Date() })
        .where(and(eq(conversations.id, id), eq(conversations.userId, userId)))
        .returning();
      return rows[0];
    },

    /**
     * Delete a conversation, scoped to the given user.
     * Returns false if the conversation does not exist or belongs to a different user.
     */
    async remove(id: string, userId: string): Promise<boolean> {
      const rows = await db
        .delete(conversations)
        .where(and(eq(conversations.id, id), eq(conversations.userId, userId)))
        .returning();
      return rows.length > 0;
    },

    /**
     * Find messages for a conversation, scoped to the given user.
     * Returns an empty array if the conversation does not exist or belongs to a different user.
     */
    async findMessages(conversationId: string, userId: string): Promise<Message[]> {
      // Verify ownership of the parent conversation before returning messages.
      const conv = await db
        .select()
        .from(conversations)
        .where(and(eq(conversations.id, conversationId), eq(conversations.userId, userId)));
      if (conv.length === 0) return [];

      return db
        .select()
        .from(messages)
        .where(eq(messages.conversationId, conversationId))
        .orderBy(asc(messages.createdAt))
        .limit(MAX_MESSAGES);
    },

    /**
     * Search messages by content across all conversations belonging to the user.
     * Uses ILIKE for case-insensitive substring matching.
     */
    async searchMessages(
      userId: string,
      query: string,
      limit: number,
      offset: number,
    ): Promise<MessageSearchResult[]> {
      const rows = await db
        .select({
          messageId: messages.id,
          conversationId: conversations.id,
          conversationTitle: conversations.title,
          role: messages.role,
          content: messages.content,
          createdAt: messages.createdAt,
        })
        .from(messages)
        .innerJoin(conversations, eq(messages.conversationId, conversations.id))
        .where(and(eq(conversations.userId, userId), ilike(messages.content, `%${query}%`)))
        .orderBy(desc(messages.createdAt))
        .limit(limit)
        .offset(offset);

      return rows;
    },

    /**
     * Add a message to a conversation, scoped to the given user.
     * Verifies the parent conversation belongs to the user before inserting.
     * Returns undefined if the conversation does not exist or belongs to a different user.
     */
    async addMessage(data: NewMessage, userId: string): Promise<Message | undefined> {
      // Verify ownership of the parent conversation before inserting the message.
      const conv = await db
        .select()
        .from(conversations)
        .where(and(eq(conversations.id, data.conversationId), eq(conversations.userId, userId)));
      if (conv.length === 0) return undefined;

      const rows = await db.insert(messages).values(data).returning();
      return rows[0]!;
    },
  };
}

export type ConversationsRepo = ReturnType<typeof createConversationsRepo>;
