const pull = require('../server/node_modules/pull-stream')
const moment = require('../server/node_modules/moment')

module.exports = ({ cooler }) => {
  let ssb
  const openSsb = async () => { if (!ssb) ssb = await cooler.open(); return ssb }

  return {
    type: 'bookmark',

    async createBookmark(url, tagsRaw, description, category, lastVisit) {
      const ssbClient = await openSsb()
      const userId = ssbClient.id
      let tags = Array.isArray(tagsRaw) ? tagsRaw.filter(t => t) : tagsRaw.split(',').map(t => t.trim()).filter(Boolean)
      const isInternal = url.includes('127.0.0.1') || url.includes('localhost')
      if (!tags.includes(isInternal ? 'internal' : 'external')) {
        tags.push(isInternal ? 'internal' : 'external')
      }
      const formattedLastVisit = lastVisit
        ? moment(lastVisit, moment.ISO_8601, true).toISOString()
        : moment().toISOString()
      const content = {
        type: 'bookmark',
        author: userId,
        url,
        tags,
        description,
        category,
        createdAt: new Date().toISOString(),
        updatedAt: new Date().toISOString(),
        lastVisit: formattedLastVisit,
        opinions: {},
        opinions_inhabitants: []
      }
      return new Promise((resolve, reject) => {
        ssbClient.publish(content, (err, res) => err ? reject(new Error("Error creating bookmark: " + err.message)) : resolve(res))
      })
    },

    async listAll(author = null, filter = 'all') {
      const ssbClient = await openSsb()
      const userId = ssbClient.id
      const results = await new Promise((res, rej) => {
        pull(
          ssbClient.createLogStream(),
          pull.collect((err, msgs) => err ? rej(err) : res(msgs))
        )
      })
      const tombstoned = new Set(
        results
          .filter(m => m.value.content?.type === 'tombstone')
          .map(m => m.value.content.target)
      )
      const replaces = new Map()
      const latest = new Map()
      for (const m of results) {
        const k = m.key
        const c = m.value.content
        if (!c || c.type !== 'bookmark') continue
        if (tombstoned.has(k)) continue
        if (c.replaces) replaces.set(c.replaces, k)
        latest.set(k, {
          id: k,
          url: c.url,
          description: c.description,
          category: c.category,
          createdAt: c.createdAt,
          lastVisit: c.lastVisit,
          tags: c.tags || [],
          opinions: c.opinions || {},
          opinions_inhabitants: c.opinions_inhabitants || [],
          author: c.author
        })
      }
      for (const oldId of replaces.keys()) {
        latest.delete(oldId)
      }
      let bookmarks = Array.from(latest.values())
      if (filter === 'mine' && author === userId) {
        bookmarks = bookmarks.filter(b => b.author === author)
      } else if (filter === 'external') {
        bookmarks = bookmarks.filter(b => b.tags.includes('external'))
      } else if (filter === 'internal') {
        bookmarks = bookmarks.filter(b => b.tags.includes('internal'))
      }
      return bookmarks
    },

    async updateBookmarkById(bookmarkId, updatedData) {
      const ssbClient = await openSsb()
      const userId = ssbClient.id
      const old = await new Promise((res, rej) =>
        ssbClient.get(bookmarkId, (err, msg) =>
          err || !msg?.content ? rej(err || new Error("Error retrieving old bookmark.")) : res(msg)
        )
      )
      if (Object.keys(old.content.opinions || {}).length > 0) {
        throw new Error('Cannot edit bookmark after it has received opinions.')
      }
      const tags = updatedData.tags
        ? updatedData.tags.split(',').map(t => t.trim()).filter(Boolean)
        : []
      const isInternal = updatedData.url.includes('127.0.0.1') || updatedData.url.includes('localhost')
      if (!tags.includes(isInternal ? 'internal' : 'external')) {
        tags.push(isInternal ? 'internal' : 'external')
      }
      const formattedLastVisit = updatedData.lastVisit
        ? moment(updatedData.lastVisit, moment.ISO_8601, true).toISOString()
        : moment().toISOString()
      const updated = {
        type: 'bookmark',
        replaces: bookmarkId,
        author: old.content.author,
        url: updatedData.url,
        tags,
        description: updatedData.description,
        category: updatedData.category,
        createdAt: old.content.createdAt,
        updatedAt: new Date().toISOString(),
        lastVisit: formattedLastVisit,
        opinions: old.content.opinions,
        opinions_inhabitants: old.content.opinions_inhabitants
      }
      return new Promise((resolve, reject) => {
        ssbClient.publish(updated, (err2, res) => err2 ? reject(new Error("Error creating updated bookmark.")) : resolve(res))
      })
    },

    async deleteBookmarkById(bookmarkId) {
      const ssbClient = await openSsb()
      const userId = ssbClient.id
      const msg = await new Promise((res, rej) =>
        ssbClient.get(bookmarkId, (err, m) => err ? rej(new Error("Error retrieving bookmark.")) : res(m))
      )
      if (msg.content.author !== userId) throw new Error("Error: You are not the author of this bookmark.")
      const tombstone = {
        type: 'tombstone',
        target: bookmarkId,
        deletedAt: new Date().toISOString(),
        author: userId
      }
      return new Promise((resolve, reject) => {
        ssbClient.publish(tombstone, (err2, res) => {
          if (err2) return reject(new Error("Error creating tombstone."))
          resolve(res)
        })
      })
    },

    async getBookmarkById(bookmarkId) {
      const ssbClient = await openSsb()
      return new Promise((resolve, reject) => {
        ssbClient.get(bookmarkId, (err, msg) => {
          if (err || !msg || !msg.content) return reject(new Error("Error retrieving bookmark"))
          const c = msg.content
          resolve({
            id: bookmarkId,
            url: c.url || "Unknown",
            description: c.description || "No description",
            category: c.category || "No category",
            createdAt: c.createdAt || "Unknown",
            updatedAt: c.updatedAt || "Unknown",
            lastVisit: c.lastVisit || "Unknown",
            tags: c.tags || [],
            opinions: c.opinions || {},
            opinions_inhabitants: c.opinions_inhabitants || [],
            author: c.author || "Unknown"
          })
        })
      })
    },

    async createOpinion(bookmarkId, category) {
      const ssbClient = await openSsb()
      const userId = ssbClient.id
      return new Promise((resolve, reject) => {
        ssbClient.get(bookmarkId, (err, msg) => {
          if (err || !msg || msg.content?.type !== 'bookmark') return reject(new Error('Bookmark not found'))
          if (msg.content.opinions_inhabitants?.includes(userId)) return reject(new Error('Already voted'))
          const updated = {
            ...msg.content,
            replaces: bookmarkId,
            opinions: {
              ...msg.content.opinions,
              [category]: (msg.content.opinions?.[category] || 0) + 1
            },
            opinions_inhabitants: [...(msg.content.opinions_inhabitants || []), userId],
            updatedAt: new Date().toISOString()
          }
          ssbClient.publish(updated, (err3, result) => err3 ? reject(err3) : resolve(result))
        })
      })
    }
  }
}

