import { getAIProvider, getChatProvider } from '../factory'
import { cosineSimilarity } from '@/lib/utils'
import { getSystemConfig } from '@/lib/config'
import prisma from '@/lib/prisma'

export interface NoteConnection {
  note1: {
    id: string
    title: string | null
    content: string
    createdAt: Date
  }
  note2: {
    id: string
    title: string | null
    content: string | null
    createdAt: Date
  }
  similarityScore: number
  insight: string
  daysApart: number
}

export interface MemoryEchoInsight {
  id: string
  note1Id: string
  note2Id: string
  note1: {
    id: string
    title: string | null
    content: string
  }
  note2: {
    id: string
    title: string | null
    content: string
  }
  similarityScore: number
  insight: string
  insightDate: Date
  viewed: boolean
  feedback: string | null
}

/**
 * Memory Echo Service - Proactive note connections
 * "I didn't search, it found me"
 */
export class MemoryEchoService {
  private readonly SIMILARITY_THRESHOLD = 0.75 // High threshold for quality connections
  private readonly SIMILARITY_THRESHOLD_DEMO = 0.50 // Lower threshold for demo mode
  private readonly MIN_DAYS_APART = 7 // Notes must be at least 7 days apart
  private readonly MIN_DAYS_APART_DEMO = 0 // No delay for demo mode
  private readonly MAX_INSIGHTS_PER_USER = 100 // Prevent spam

  /**
   * Generate embeddings for notes that don't have one yet
   */
  private async ensureEmbeddings(userId: string): Promise<void> {
    const notesWithoutEmbeddings = await prisma.note.findMany({
      where: {
        userId,
        isArchived: false,
        trashedAt: null,
        noteEmbedding: { is: null }
      },
      select: { id: true, content: true }
    })

    if (notesWithoutEmbeddings.length === 0) return

    try {
      const config = await getSystemConfig()
      const provider = getAIProvider(config)

      for (const note of notesWithoutEmbeddings) {
        if (!note.content || note.content.trim().length === 0) continue
        try {
          const embedding = await provider.getEmbeddings(note.content)
          if (embedding && embedding.length > 0) {
            await prisma.noteEmbedding.upsert({
              where: { noteId: note.id },
              create: { noteId: note.id, embedding: JSON.stringify(embedding) },
              update: { embedding: JSON.stringify(embedding) }
            })
          }
        } catch {
          // Skip this note, continue with others
        }
      }
    } catch {
      // Provider not configured — nothing we can do
    }
  }

  /**
   * Find meaningful connections between user's notes
   */
  async findConnections(userId: string, demoMode: boolean = false): Promise<NoteConnection[]> {
    // Ensure all notes have embeddings before searching for connections
    await this.ensureEmbeddings(userId)

    // Get all user's notes with embeddings
    const notes = await prisma.note.findMany({
      where: {
        userId,
        isArchived: false,
        trashedAt: null,
        noteEmbedding: { isNot: null } // Only notes with embeddings
      },
      select: {
        id: true,
        title: true,
        content: true,
        noteEmbedding: true,
        createdAt: true
      },
      orderBy: { createdAt: 'desc' }
    })

    if (notes.length < 2) {
      return [] // Need at least 2 notes to find connections
    }

    // Parse embeddings (already native Json from PostgreSQL)
    const notesWithEmbeddings = notes
      .map(note => ({
        ...note,
        embedding: note.noteEmbedding?.embedding ? JSON.parse(note.noteEmbedding.embedding) as number[] : null
      }))
      .filter(note => note.embedding && Array.isArray(note.embedding))

    const connections: NoteConnection[] = []

    // Use demo mode parameters if enabled
    const minDaysApart = demoMode ? this.MIN_DAYS_APART_DEMO : this.MIN_DAYS_APART
    const similarityThreshold = demoMode ? this.SIMILARITY_THRESHOLD_DEMO : this.SIMILARITY_THRESHOLD

    // Load user feedback to adjust thresholds per note
    const feedbackInsights = await prisma.memoryEchoInsight.findMany({
      where: { userId, feedback: { not: null } },
      select: { note1Id: true, note2Id: true, feedback: true }
    })

    const notePenalty = new Map<string, number>() // positive = higher threshold (penalty), negative = lower (boost)
    for (const fi of feedbackInsights) {
      if (fi.feedback === 'thumbs_down') {
        notePenalty.set(fi.note1Id, (notePenalty.get(fi.note1Id) || 0) + 0.15)
        notePenalty.set(fi.note2Id, (notePenalty.get(fi.note2Id) || 0) + 0.15)
      } else if (fi.feedback === 'thumbs_up') {
        notePenalty.set(fi.note1Id, (notePenalty.get(fi.note1Id) || 0) - 0.05)
        notePenalty.set(fi.note2Id, (notePenalty.get(fi.note2Id) || 0) - 0.05)
      }
    }

    // Compare all pairs of notes
    for (let i = 0; i < notesWithEmbeddings.length; i++) {
      for (let j = i + 1; j < notesWithEmbeddings.length; j++) {
        const note1 = notesWithEmbeddings[i]
        const note2 = notesWithEmbeddings[j]

        // Calculate time difference
        const daysApart = Math.abs(
          Math.floor((note1.createdAt.getTime() - note2.createdAt.getTime()) / (1000 * 60 * 60 * 24))
        )

        // Time diversity filter: notes must be from different time periods
        if (daysApart < minDaysApart) {
          continue
        }

        // Calculate cosine similarity
        const similarity = cosineSimilarity(note1.embedding!, note2.embedding!)

        // Similarity threshold for meaningful connections (adjusted by feedback)
        const adjustedThreshold = similarityThreshold
          + (notePenalty.get(note1.id) || 0)
          + (notePenalty.get(note2.id) || 0)
        if (similarity >= adjustedThreshold) {
          connections.push({
            note1: {
              id: note1.id,
              title: note1.title,
              content: note1.content.substring(0, 200) + (note1.content.length > 200 ? '...' : ''),
              createdAt: note1.createdAt
            },
            note2: {
              id: note2.id,
              title: note2.title,
              content: note2.content ? note2.content.substring(0, 200) + (note2.content.length > 200 ? '...' : '') : '',
              createdAt: note2.createdAt
            },
            similarityScore: similarity,
            insight: '', // Will be generated by AI
            daysApart
          })
        }
      }
    }

    // Sort by similarity score (descending)
    connections.sort((a, b) => b.similarityScore - a.similarityScore)

    // Return top connections
    return connections.slice(0, 10)
  }

  /**
   * Generate AI explanation for the connection
   */
  async generateInsight(
    note1Title: string | null,
    note1Content: string,
    note2Title: string | null,
    note2Content: string
  ): Promise<string> {
    try {
      const config = await getSystemConfig()
      const provider = getChatProvider(config)

      const note1Desc = note1Title || 'Untitled note'
      const note2Desc = note2Title || 'Untitled note'

      const prompt = `You are a helpful assistant analyzing connections between notes.

Note 1: "${note1Desc}"
Content: ${note1Content.substring(0, 300)}

Note 2: "${note2Desc}"
Content: ${note2Content.substring(0, 300)}

Explain in one brief sentence (max 15 words) why these notes are connected. Focus on the semantic relationship.`

      const response = await provider.generateText(prompt)

      // Clean up response
      const insight = response
        .replace(/^["']|["']$/g, '') // Remove quotes
        .replace(/^[^.]+\.\s*/, '') // Remove "Here is..." prefix
        .trim()
        .substring(0, 150) // Max length

      return insight || 'These notes appear to be semantically related.'

    } catch (error) {
      console.error('[MemoryEcho] Failed to generate insight:', error)
      return 'These notes appear to be semantically related.'
    }
  }

  /**
   * Get next pending insight for user (based on frequency limit)
   */
  async getNextInsight(userId: string): Promise<MemoryEchoInsight | null> {
    // Check if Memory Echo is enabled for user
    const settings = await prisma.userAISettings.findUnique({
      where: { userId }
    })

    if (!settings || !settings.memoryEcho) {
      return null // Memory Echo disabled
    }

    const demoMode = settings.demoMode || false

    // Skip frequency checks in demo mode
    if (!demoMode) {
      // Check frequency limit
      const today = new Date()
      today.setHours(0, 0, 0, 0)

      const insightsShownToday = await prisma.memoryEchoInsight.count({
        where: {
          userId,
          insightDate: {
            gte: today
          }
        }
      })

      // Frequency limits
      const maxPerDay = settings.memoryEchoFrequency === 'daily' ? 1 :
                        settings.memoryEchoFrequency === 'weekly' ? 0 : // 1 per 7 days (handled below)
                        3 // custom = 3 per day

      if (settings.memoryEchoFrequency === 'weekly') {
        // Check if shown in last 7 days
        const weekAgo = new Date(today)
        weekAgo.setDate(weekAgo.getDate() - 7)

        const recentInsight = await prisma.memoryEchoInsight.findFirst({
          where: {
            userId,
            insightDate: {
              gte: weekAgo
            }
          }
        })

        if (recentInsight) {
          return null // Already shown this week
        }
      } else if (insightsShownToday >= maxPerDay) {
        return null // Daily limit reached
      }

      // Check total insights limit (prevent spam)
      const totalInsights = await prisma.memoryEchoInsight.count({
        where: { userId }
      })

      if (totalInsights >= this.MAX_INSIGHTS_PER_USER) {
        return null // User has too many insights
      }
    }

    // Find new connections (pass demoMode)
    const connections = await this.findConnections(userId, demoMode)

    if (connections.length === 0) {
      return null // No connections found
    }

    // Filter out already shown connections
    const existingInsights = await prisma.memoryEchoInsight.findMany({
      where: { userId },
      select: { note1Id: true, note2Id: true }
    })

    const shownPairs = new Set(
      existingInsights.map(i => `${i.note1Id}-${i.note2Id}`)
    )

    const newConnection = connections.find(c =>
      !shownPairs.has(`${c.note1.id}-${c.note2.id}`) &&
      !shownPairs.has(`${c.note2.id}-${c.note1.id}`)
    )

    if (!newConnection) {
      return null // All connections already shown
    }

    // Generate AI insight
    const insightText = await this.generateInsight(
      newConnection.note1.title,
      newConnection.note1.content,
      newConnection.note2.title,
      newConnection.note2.content || ''
    )

    // Store insight in database
    // In demo mode, add milliseconds offset to avoid @@unique([userId, insightDate]) collision
    const insightDateValue = demoMode
      ? new Date(Date.now() + Math.floor(Math.random() * 1000))
      : new Date()

    const insight = await prisma.memoryEchoInsight.create({
      data: {
        userId,
        note1Id: newConnection.note1.id,
        note2Id: newConnection.note2.id,
        similarityScore: newConnection.similarityScore,
        insight: insightText,
        insightDate: insightDateValue,
        viewed: false
      },
      include: {
        note1: {
          select: {
            id: true,
            title: true,
            content: true
          }
        },
        note2: {
          select: {
            id: true,
            title: true,
            content: true
          }
        }
      }
    })

    return insight
  }

  /**
   * Mark insight as viewed
   */
  async markAsViewed(insightId: string): Promise<void> {
    await prisma.memoryEchoInsight.update({
      where: { id: insightId },
      data: { viewed: true }
    })
  }

  /**
   * Submit feedback for insight
   */
  async submitFeedback(insightId: string, feedback: 'thumbs_up' | 'thumbs_down'): Promise<void> {
    await prisma.memoryEchoInsight.update({
      where: { id: insightId },
      data: { feedback }
    })

    // Optional: Store in AiFeedback for analytics
    const insight = await prisma.memoryEchoInsight.findUnique({
      where: { id: insightId },
      select: { userId: true, note1Id: true }
    })

    if (insight) {
      await prisma.aiFeedback.create({
        data: {
          noteId: insight.note1Id,
          userId: insight.userId,
          feedbackType: feedback,
          feature: 'memory_echo',
          originalContent: JSON.stringify({ insightId }),
          metadata: {
            timestamp: new Date().toISOString()
          } as any
        }
      })
    }
  }

  /**
   * Get all connections for a specific note
   */
  async getConnectionsForNote(noteId: string, userId: string): Promise<NoteConnection[]> {
    // Ensure all notes have embeddings before searching
    await this.ensureEmbeddings(userId)

    // Get the note with embedding
    const targetNote = await prisma.note.findUnique({
      where: { id: noteId },
      select: {
        id: true,
        title: true,
        content: true,
        noteEmbedding: true,
        createdAt: true,
        userId: true
      }
    })

    if (!targetNote || targetNote.userId !== userId) {
      return [] // Note not found or doesn't belong to user
    }

    if (!targetNote.noteEmbedding) {
      return [] // Note has no embedding
    }

    // Get dismissed connections for this note (to filter them out)
    const dismissedInsights = await prisma.memoryEchoInsight.findMany({
      where: {
        userId,
        dismissed: true,
        OR: [
          { note1Id: noteId },
          { note2Id: noteId }
        ]
      },
      select: {
        note1Id: true,
        note2Id: true
      }
    })

    // Create a set of dismissed note pairs for quick lookup
    const dismissedPairs = new Set(
      dismissedInsights.map(i =>
        `${i.note1Id}-${i.note2Id}`
      )
    )

    // Get all other user's notes with embeddings
    const otherNotes = await prisma.note.findMany({
      where: {
        userId,
        id: { not: noteId },
        isArchived: false,
        trashedAt: null,
        noteEmbedding: { isNot: null }
      },
      select: {
        id: true,
        title: true,
        content: true,
        noteEmbedding: true,
        createdAt: true
      },
      orderBy: { createdAt: 'desc' }
    })

    if (otherNotes.length === 0) {
      return []
    }

    // Target note embedding (already native Json from PostgreSQL)
    const targetEmbedding = targetNote.noteEmbedding?.embedding ? JSON.parse(targetNote.noteEmbedding.embedding) as number[] : null
    if (!targetEmbedding) return []

    // Check if user has demo mode enabled
    const settings = await prisma.userAISettings.findUnique({
      where: { userId }
    })
    const demoMode = settings?.demoMode || false

    const minDaysApart = demoMode ? this.MIN_DAYS_APART_DEMO : this.MIN_DAYS_APART
    const similarityThreshold = demoMode ? this.SIMILARITY_THRESHOLD_DEMO : this.SIMILARITY_THRESHOLD

    // Load user feedback to adjust thresholds
    const feedbackInsights = await prisma.memoryEchoInsight.findMany({
      where: { userId, feedback: { not: null } },
      select: { note1Id: true, note2Id: true, feedback: true }
    })
    const notePenalty = new Map<string, number>()
    for (const fi of feedbackInsights) {
      if (fi.feedback === 'thumbs_down') {
        notePenalty.set(fi.note1Id, (notePenalty.get(fi.note1Id) || 0) + 0.15)
        notePenalty.set(fi.note2Id, (notePenalty.get(fi.note2Id) || 0) + 0.15)
      } else if (fi.feedback === 'thumbs_up') {
        notePenalty.set(fi.note1Id, (notePenalty.get(fi.note1Id) || 0) - 0.05)
        notePenalty.set(fi.note2Id, (notePenalty.get(fi.note2Id) || 0) - 0.05)
      }
    }

    const connections: NoteConnection[] = []

    // Compare target note with all other notes
    for (const otherNote of otherNotes) {
      if (!otherNote.noteEmbedding) continue

      const otherEmbedding = otherNote.noteEmbedding?.embedding ? JSON.parse(otherNote.noteEmbedding.embedding) as number[] : null
      if (!otherEmbedding) continue

      // Check if this connection was dismissed
      const pairKey1 = `${targetNote.id}-${otherNote.id}`
      const pairKey2 = `${otherNote.id}-${targetNote.id}`
      if (dismissedPairs.has(pairKey1) || dismissedPairs.has(pairKey2)) {
        continue
      }

      // Calculate time difference
      const daysApart = Math.abs(
        Math.floor((targetNote.createdAt.getTime() - otherNote.createdAt.getTime()) / (1000 * 60 * 60 * 24))
      )

      // Time diversity filter
      if (daysApart < minDaysApart) {
        continue
      }

      // Calculate cosine similarity
      const similarity = cosineSimilarity(targetEmbedding, otherEmbedding)

      // Similarity threshold (adjusted by feedback)
      const adjustedThreshold = similarityThreshold
        + (notePenalty.get(targetNote.id) || 0)
        + (notePenalty.get(otherNote.id) || 0)
      if (similarity >= adjustedThreshold) {
        connections.push({
          note1: {
            id: targetNote.id,
            title: targetNote.title,
            content: targetNote.content.substring(0, 200) + (targetNote.content.length > 200 ? '...' : ''),
            createdAt: targetNote.createdAt
          },
          note2: {
            id: otherNote.id,
            title: otherNote.title,
            content: otherNote.content ? otherNote.content.substring(0, 200) + (otherNote.content.length > 200 ? '...' : '') : '',
            createdAt: otherNote.createdAt
          },
          similarityScore: similarity,
          insight: '', // Will be generated on demand
          daysApart
        })
      }
    }

    // Sort by similarity score (descending)
    connections.sort((a, b) => b.similarityScore - a.similarityScore)

    return connections
  }

  /**
   * Get insights history for user
   */
  async getInsightsHistory(userId: string): Promise<MemoryEchoInsight[]> {
    const insights = await prisma.memoryEchoInsight.findMany({
      where: { userId },
      include: {
        note1: {
          select: {
            id: true,
            title: true,
            content: true
          }
        },
        note2: {
          select: {
            id: true,
            title: true,
            content: true
          }
        }
      },
      orderBy: { insightDate: 'desc' },
      take: 20
    })

    return insights
  }
}

// Export singleton instance
export const memoryEchoService = new MemoryEchoService()
