import { SimplePool, type Filter, type Event } from 'nostr-tools'
import { getReplyInfo, EventKinds } from './events'

export interface NostrClientConfig {
  relays: string[]
}

export interface NostrNote extends Event {
  // Add any additional note-specific fields we want to track
  replyCount: number
  reactionCount: number
  reactions: { [reaction: string]: number }
  isReply: boolean
  replyTo?: string
  mentions: string[]
}

export class NostrClient {
  private pool: SimplePool
  private relays: string[]
  private _isConnected: boolean = false

  constructor(config: NostrClientConfig) {
    this.pool = new SimplePool()
    this.relays = config.relays
  }

  get isConnected(): boolean {
    return this._isConnected
  }

  async connect(): Promise<void> {
    try {
      // Try to connect to at least one relay
      const connections = await Promise.allSettled(
        this.relays.map(relay => this.pool.ensureRelay(relay))
      )

      // Check if at least one connection was successful
      this._isConnected = connections.some(result => result.status === 'fulfilled')

      if (!this._isConnected) {
        throw new Error('Failed to connect to any relay')
      }
    } catch (error) {
      this._isConnected = false
      throw error
    }
  }

  disconnect(): void {
    this.pool.close(this.relays)
    this._isConnected = false
  }

  async fetchNotes(options: { 
    limit?: number 
    since?: number // Unix timestamp in seconds
    authors?: string[]
    includeReplies?: boolean
  } = {}): Promise<NostrNote[]> {
    const { 
      limit = 20, 
      authors,
      includeReplies = false
    } = options

    const filters: Filter[] = [
      {
        kinds: [EventKinds.TEXT_NOTE],
        limit,
        ...(authors && { authors })
      }
    ]

    try {
      // Use proper subscription method to get multiple events
      const allEvents: Event[] = []
      
      const subscription = this.pool.subscribeMany(this.relays, filters, {
        onevent(event: Event) {
          allEvents.push(event)
        },
        oneose() {
          // End of stored events - subscription is complete for initial fetch
        }
      })
      
      // Wait for events to be collected (give it time to get all stored events)
      await new Promise(resolve => setTimeout(resolve, 2000))
      
      // Close the subscription since we just want the initial fetch
      subscription.close()
      
      const noteEvents = [allEvents] // Wrap in array to match expected format

      // Flatten and deduplicate events by ID
      const uniqueNotes = Array.from(
        new Map(
          noteEvents.flat().map(event => [event.id, event])
        ).values()
      )

      // Process notes with basic info (engagement data disabled for now)
      let processedNotes = uniqueNotes
        .map((event: Event): NostrNote => {
          const replyInfo = getReplyInfo(event)

          return {
            ...event,
            replyCount: 0,
            reactionCount: 0,
            reactions: {},
            isReply: replyInfo.isReply,
            replyTo: replyInfo.replyTo,
            mentions: replyInfo.mentions
          }
        })

      // Filter out replies if not requested
      if (!includeReplies) {
        processedNotes = processedNotes.filter(note => !note.isReply)
      }

      return processedNotes
        .sort((a: NostrNote, b: NostrNote) => b.created_at - a.created_at) // Sort by newest first
        .slice(0, limit) // Apply limit after processing
    } catch (error) {
      console.error('Failed to fetch notes:', error)
      throw error
    }
  }

  /**
   * Publish an event to all connected relays
   */
  async publishEvent(event: Event): Promise<void> {
    if (!this._isConnected) {
      throw new Error('Not connected to any relays')
    }

    try {
      const results = await Promise.allSettled(
        this.relays.map(relay => this.pool.publish([relay], event))
      )

      const failures = results.filter(result => result.status === 'rejected')
      if (failures.length === results.length) {
        throw new Error('Failed to publish to any relay')
      }

      console.log(`Published event ${event.id} to ${results.length - failures.length}/${results.length} relays`)
    } catch (error) {
      console.error('Failed to publish event:', error)
      throw error
    }
  }

  /**
   * Fetch replies to a specific note
   */
  async fetchReplies(noteId: string, limit: number = 50): Promise<NostrNote[]> {
    const filter: Filter = {
      kinds: [EventKinds.TEXT_NOTE],
      '#e': [noteId],
      limit
    }

    try {
      const events = await Promise.all(
        this.relays.map(async (relay) => {
          try {
            return await this.pool.querySync([relay], filter)
          } catch (error) {
            console.warn(`Failed to fetch replies from relay ${relay}:`, error)
            return []
          }
        })
      )

      // Flatten and deduplicate events by ID
      const uniqueEvents = Array.from(
        new Map(
          events.flat().map(event => [event.id, event])
        ).values()
      )

      return uniqueEvents
        .sort((a: Event, b: Event) => a.created_at - b.created_at) // Chronological order for replies
        .map((event: Event): NostrNote => {
          const replyInfo = getReplyInfo(event)
          return {
            ...event,
            replyCount: 0, // Individual replies don't need reply counts in this context
            reactionCount: 0, // We could add this later
            reactions: {},
            isReply: replyInfo.isReply,
            replyTo: replyInfo.replyTo,
            mentions: replyInfo.mentions
          }
        })
    } catch (error) {
      console.error('Failed to fetch replies:', error)
      throw error
    }
  }

  /**
   * Fetch user profiles
   */
  async fetchProfiles(pubkeys: string[]): Promise<Map<string, any>> {
    if (pubkeys.length === 0) return new Map()

    const filter: Filter = {
      kinds: [EventKinds.PROFILE_METADATA],
      authors: pubkeys
    }

    try {
      const events = await Promise.all(
        this.relays.map(async (relay) => {
          try {
            return await this.pool.querySync([relay], filter)
          } catch (error) {
            console.warn(`Failed to fetch profiles from relay ${relay}:`, error)
            return []
          }
        })
      )

      const profiles = new Map<string, any>()
      const allEvents = events.flat()

      // Get the latest profile for each pubkey
      pubkeys.forEach(pubkey => {
        const userEvents = allEvents
          .filter(event => event.pubkey === pubkey)
          .sort((a, b) => b.created_at - a.created_at)
        
        if (userEvents.length > 0) {
          try {
            const profile = JSON.parse(userEvents[0].content)
            profiles.set(pubkey, profile)
          } catch (error) {
            console.warn(`Failed to parse profile for ${pubkey}:`, error)
          }
        }
      })

      return profiles
    } catch (error) {
      console.error('Failed to fetch profiles:', error)
      throw error
    }
  }

  // Subscribe to new notes in real-time
  subscribeToNotes(onNote: (note: NostrNote) => void): () => void {
    const filters = [{
      kinds: [EventKinds.TEXT_NOTE],
      since: Math.floor(Date.now() / 1000)
    }]

    // Subscribe to each relay individually
    const unsubscribes = this.relays.map(relay => {
      const sub = this.pool.subscribeMany(
        [relay],
        filters,
        {
          onevent: (event: Event) => {
            const replyInfo = getReplyInfo(event)
            onNote({
              ...event,
              replyCount: 0,
              reactionCount: 0,
              reactions: {},
              isReply: replyInfo.isReply,
              replyTo: replyInfo.replyTo,
              mentions: replyInfo.mentions
            })
          }
        }
      )
      return () => sub.close()
    })

    // Return a function that unsubscribes from all relays
    return () => {
      unsubscribes.forEach(unsub => unsub())
    }
  }
} 