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

const extractBlobId = str => {
  if (!str || typeof str !== 'string') return null
  const match = str.match(/\(([^)]+\.sha256)\)/)
  return match ? match[1] : str.trim()
}

const parseCSV = str => str ? str.split(',').map(s => s.trim()).filter(Boolean) : []

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

  return {
    type: 'document',

    async createDocument(blobMarkdown, tagsRaw, title, description) {
      const ssbClient = await openSsb()
      const userId = ssbClient.id
      const blobId = extractBlobId(blobMarkdown)
      const tags = parseCSV(tagsRaw)
      const content = {
        type: 'document',
        url: blobId,
        createdAt: new Date().toISOString(),
        author: userId,
        tags,
        title: title || '',
        description: description || '',
        opinions: {},
        opinions_inhabitants: []
      }
      return new Promise((resolve, reject) => {
        ssbClient.publish(content, (err, res) => err ? reject(err) : resolve(res))
      })
    },

    async updateDocumentById(id, blobMarkdown, tagsRaw, title, description) {
      const ssbClient = await openSsb()
      const userId = ssbClient.id
      return new Promise((resolve, reject) => {
        ssbClient.get(id, (err, oldMsg) => {
          if (err || !oldMsg || oldMsg.content?.type !== 'document') return reject(new Error('Document not found'))
          if (Object.keys(oldMsg.content.opinions || {}).length > 0) return reject(new Error('Cannot edit document after it has received opinions.'))
          if (oldMsg.content.author !== userId) return reject(new Error('Not the author'))
          const tags = parseCSV(tagsRaw)
          const blobId = extractBlobId(blobMarkdown)
          const updated = {
            ...oldMsg.content,
            replaces: id,
            url: blobId || oldMsg.content.url,
            tags,
            title: title || '',
            description: description || '',
            updatedAt: new Date().toISOString()
          }
          ssbClient.publish(updated, (err3, result) => err3 ? reject(err3) : resolve(result))
        })
      })
    },

    async deleteDocumentById(id) {
      const ssbClient = await openSsb()
      const userId = ssbClient.id
      return new Promise((resolve, reject) => {
        ssbClient.get(id, (err, msg) => {
          if (err || !msg || msg.content?.type !== 'document') return reject(new Error('Document not found'))
          if (msg.content.author !== userId) return reject(new Error('Not the author'))
          const tombstone = {
            type: 'tombstone',
            target: id,
            deletedAt: new Date().toISOString(),
            author: userId
          }
          ssbClient.publish(tombstone, (err2, res) => err2 ? reject(err2) : resolve(res))
        })
      })
    },

    async listAll(filter = 'all') {
      const ssbClient = await openSsb()
      const userId = ssbClient.id
      const messages = await new Promise((res, rej) => {
        pull(
          ssbClient.createLogStream(),
          pull.collect((err, msgs) => err ? rej(err) : res(msgs))
        )
      })
      const tombstoned = new Set(
        messages
          .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 messages) {
        const k = m.key
        const c = m.value?.content
        if (!c || c.type !== 'document') continue
        if (tombstoned.has(k)) continue
        if (c.replaces) replaces.set(c.replaces, k)
        latest.set(k, {
          key: k,
          url: c.url,
          createdAt: c.createdAt,
          updatedAt: c.updatedAt || null,
          tags: c.tags || [],
          author: c.author,
          title: c.title || '',
          description: c.description || '',
          opinions: c.opinions || {},
          opinions_inhabitants: c.opinions_inhabitants || []
        })
      }

      for (const oldId of replaces.keys()) {
        latest.delete(oldId)
      }
      let documents = Array.from(latest.values())
      if (filter === 'mine') {
        documents = documents.filter(d => d.author === userId)
      } else {
        documents = documents.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt))
      }
      const hasBlob = (blobId) => {
        return new Promise((resolve) => {
          ssbClient.blobs.has(blobId, (err, has) => {
            resolve(!err && has);
          });
        });
      };
     documents = await Promise.all(
        documents.map(async (doc) => {
          const ok = await hasBlob(doc.url);
          return ok ? doc : null;
        })
      );
      documents = documents.filter(Boolean);

      return documents
    },

    async getDocumentById(id) {
      const ssbClient = await openSsb()
      return new Promise((resolve, reject) => {
        ssbClient.get(id, (err, msg) => {
          if (err || !msg || msg.content?.type !== 'document') return reject(new Error('Document not found'))
          resolve({
            key: id,
            url: msg.content.url,
            createdAt: msg.content.createdAt,
            updatedAt: msg.content.updatedAt || null,
            tags: msg.content.tags || [],
            author: msg.content.author,
            title: msg.content.title || '',
            description: msg.content.description || '',
            opinions: msg.content.opinions || {},
            opinions_inhabitants: msg.content.opinions_inhabitants || []
          })
        })
      })
    },

    async createOpinion(id, category) {
      const ssbClient = await openSsb()
      const userId = ssbClient.id
      return new Promise((resolve, reject) => {
        ssbClient.get(id, (err, msg) => {
          if (err || !msg || msg.content?.type !== 'document') return reject(new Error('Document not found'))
          if (msg.content.opinions_inhabitants?.includes(userId)) return reject(new Error('Already voted'))
          const updated = {
            ...msg.content,
            replaces: id,
            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))
        })
      })
    }
  }
}

