import { eq, and, desc, sql, lt, type Db, insights } from '@mosaicstack/db';

export type Insight = typeof insights.$inferSelect;
export type NewInsight = typeof insights.$inferInsert;

export interface SearchResult {
  insight: Insight;
  distance: number;
}

export function createInsightsRepo(db: Db) {
  return {
    async findByUser(userId: string, limit = 50): Promise<Insight[]> {
      return db
        .select()
        .from(insights)
        .where(eq(insights.userId, userId))
        .orderBy(desc(insights.createdAt))
        .limit(limit);
    },

    async findById(id: string, userId: string): Promise<Insight | undefined> {
      const rows = await db
        .select()
        .from(insights)
        .where(and(eq(insights.id, id), eq(insights.userId, userId)));
      return rows[0];
    },

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

    async update(
      id: string,
      userId: string,
      data: Partial<NewInsight>,
    ): Promise<Insight | undefined> {
      const rows = await db
        .update(insights)
        .set({ ...data, updatedAt: new Date() })
        .where(and(eq(insights.id, id), eq(insights.userId, userId)))
        .returning();
      return rows[0];
    },

    async remove(id: string, userId: string): Promise<boolean> {
      const rows = await db
        .delete(insights)
        .where(and(eq(insights.id, id), eq(insights.userId, userId)))
        .returning();
      return rows.length > 0;
    },

    /**
     * Semantic search using pgvector cosine distance.
     * Requires the vector extension and an embedding for the query.
     */
    async searchByEmbedding(
      userId: string,
      queryEmbedding: number[],
      limit = 10,
      maxDistance = 0.8,
    ): Promise<SearchResult[]> {
      const embeddingStr = `[${queryEmbedding.join(',')}]`;
      const rows = await db.execute(sql`
        SELECT *,
               (embedding <=> ${embeddingStr}::vector) AS distance
        FROM insights
        WHERE user_id = ${userId}
          AND embedding IS NOT NULL
          AND (embedding <=> ${embeddingStr}::vector) < ${maxDistance}
        ORDER BY distance ASC
        LIMIT ${limit}
      `);

      return rows as unknown as SearchResult[];
    },

    /**
     * Decay relevance scores for old insights that haven't been accessed recently.
     * Scoped to a specific user to prevent cross-user data mutation.
     */
    async decayOldInsights(userId: string, olderThan: Date, decayFactor = 0.95): Promise<number> {
      const result = await db
        .update(insights)
        .set({
          relevanceScore: sql`${insights.relevanceScore} * ${decayFactor}`,
          decayedAt: new Date(),
          updatedAt: new Date(),
        })
        .where(
          and(
            eq(insights.userId, userId),
            lt(insights.updatedAt, olderThan),
            sql`${insights.relevanceScore} > 0.1`,
          ),
        )
        .returning();
      return result.length;
    },

    /**
     * Decay relevance scores for all users' old insights.
     * This is a system-level maintenance operation intended for scheduled cron jobs only.
     * Do NOT expose this through user-facing API endpoints.
     */
    async decayAllInsights(olderThan: Date, decayFactor = 0.95): Promise<number> {
      const result = await db
        .update(insights)
        .set({
          relevanceScore: sql`${insights.relevanceScore} * ${decayFactor}`,
          decayedAt: new Date(),
          updatedAt: new Date(),
        })
        .where(and(lt(insights.updatedAt, olderThan), sql`${insights.relevanceScore} > 0.1`))
        .returning();
      return result.length;
    },
  };
}

export type InsightsRepo = ReturnType<typeof createInsightsRepo>;
