const pull = require('../server/node_modules/pull-stream');
const { getConfig } = require('../configs/config-manager.js');
const { buildValidatedTombstoneSet } = require('./tombstone_validator');
const logLimit = getConfig().ssbLogStream?.limit || 1000;

const normU = (v) => String(v || '').trim().toUpperCase();
const normalizeStatus = (v) => normU(v).replace(/\s+/g, '_').replace(/-+/g, '_');
const normalizeSeverity = (v) => String(v || '').trim().toLowerCase();
const ensureArray = (v) => Array.isArray(v) ? v.filter(Boolean) : [];

const trimStr = (v) => String(v || '').trim();

const normalizeTemplate = (category, tpl) => {
  const cat = normU(category);
  const t = tpl && typeof tpl === 'object' ? tpl : {};

  const pick = (keys) => {
    const out = {};
    for (const k of keys) {
      const val = trimStr(t[k]);
      if (val) out[k] = val;
    }
    return out;
  };

  if (cat === 'BUGS') {
    const out = pick(['stepsToReproduce', 'expectedBehavior', 'actualBehavior', 'environment', 'reproduceRate']);
    if (out.reproduceRate) out.reproduceRate = normU(out.reproduceRate);
    return out;
  }

  if (cat === 'FEATURES') {
    return pick(['problemStatement', 'userStory', 'acceptanceCriteria']);
  }

  if (cat === 'ABUSE') {
    return pick(['whatHappened', 'reportedUser', 'evidenceLinks']);
  }

  if (cat === 'CONTENT') {
    return pick(['contentLocation', 'whyInappropriate', 'requestedAction', 'evidenceLinks']);
  }

  return {};
};

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

  return {
    async createReport(title, description, category, image, tagsRaw = [], severity = 'low', template = {}) {
      const ssb = await openSsb();
      const userId = ssb.id;

      let blobId = null;
      if (image) {
        blobId = String(image).trim() || null;
      }

      const tags = Array.isArray(tagsRaw)
        ? tagsRaw.filter(Boolean)
        : String(tagsRaw || '').split(',').map(t => t.trim()).filter(Boolean);

      const cat = normU(category);
      const content = {
        type: 'report',
        title,
        description,
        category: cat,
        createdAt: new Date().toISOString(),
        author: userId,
        image: blobId,
        tags,
        confirmations: [],
        severity: normalizeSeverity(severity) || 'low',
        status: 'OPEN',
        template: normalizeTemplate(cat, template),
        opinions: {},
        opinions_inhabitants: []
      };

      return new Promise((res, rej) => ssb.publish(content, (err, msg) => err ? rej(err) : res(msg)));
    },

    async updateReportById(id, updatedContent) {
      const ssb = await openSsb();
      const userId = ssb.id;

      const report = await new Promise((res, rej) =>
        ssb.get(id, (err, r) => err ? rej(new Error('Report not found')) : res(r))
      );

      if (report.content.author !== userId) throw new Error('Not the author');

      const tags = Object.prototype.hasOwnProperty.call(updatedContent, 'tags')
        ? String(updatedContent.tags || '').split(',').map(t => t.trim()).filter(Boolean)
        : ensureArray(report.content.tags);

      let blobId = report.content.image || null;
      if (updatedContent.image) {
        blobId = String(updatedContent.image).trim() || null;
      }

      const nextStatus = Object.prototype.hasOwnProperty.call(updatedContent, 'status')
        ? normalizeStatus(updatedContent.status)
        : normalizeStatus(report.content.status || 'OPEN');

      const nextSeverity = Object.prototype.hasOwnProperty.call(updatedContent, 'severity')
        ? (normalizeSeverity(updatedContent.severity) || 'low')
        : (normalizeSeverity(report.content.severity) || 'low');

      const nextCategory = Object.prototype.hasOwnProperty.call(updatedContent, 'category')
        ? normU(updatedContent.category)
        : normU(report.content.category);

      const confirmations = ensureArray(report.content.confirmations);

      const baseTemplate = Object.prototype.hasOwnProperty.call(updatedContent, 'template')
        ? updatedContent.template
        : (report.content.template || {});

      const nextTemplate = normalizeTemplate(nextCategory, baseTemplate);

      const updated = {
        ...report.content,
        ...updatedContent,
        type: 'report',
        replaces: id,
        image: blobId,
        tags,
        confirmations,
        severity: nextSeverity,
        status: nextStatus,
        category: nextCategory,
        template: nextTemplate,
        updatedAt: new Date().toISOString(),
        author: report.content.author
      };

      return new Promise((res, rej) => ssb.publish(updated, (err, result) => err ? rej(err) : res(result)));
    },

    async deleteReportById(id) {
      const ssb = await openSsb();
      const userId = ssb.id;

      const report = await new Promise((res, rej) =>
        ssb.get(id, (err, r) => err ? rej(new Error('Report not found')) : res(r))
      );

      if (report.content.author !== userId) throw new Error('Not the author');

      const tombstone = { type: 'tombstone', target: id, deletedAt: new Date().toISOString(), author: userId };
      return new Promise((res, rej) => ssb.publish(tombstone, (err, result) => err ? rej(err) : res(result)));
    },

    async getReportById(id) {
      const ssb = await openSsb();

      const report = await new Promise((res, rej) =>
        ssb.get(id, (err, r) => err ? rej(new Error('Report not found')) : res(r))
      );

      const c = report.content || {};
      const cat = normU(c.category);
      return {
        id,
        ...c,
        category: cat,
        status: normalizeStatus(c.status || 'OPEN'),
        severity: normalizeSeverity(c.severity) || 'low',
        confirmations: ensureArray(c.confirmations),
        tags: ensureArray(c.tags),
        template: normalizeTemplate(cat, c.template || {})
      };
    },

    async confirmReportById(id) {
      const ssb = await openSsb();
      const userId = ssb.id;

      const report = await new Promise((res, rej) =>
        ssb.get(id, (err, r) => err ? rej(new Error('Report not found')) : res(r))
      );

      const confirmations = ensureArray(report.content.confirmations);
      if (confirmations.includes(userId)) throw new Error('Already confirmed');

      const cat = normU(report.content.category);
      const updated = {
        ...report.content,
        type: 'report',
        replaces: id,
        confirmations: [...confirmations, userId],
        updatedAt: new Date().toISOString(),
        status: normalizeStatus(report.content.status || 'OPEN'),
        category: cat,
        severity: normalizeSeverity(report.content.severity) || 'low',
        template: normalizeTemplate(cat, report.content.template || {})
      };

      return new Promise((res, rej) => ssb.publish(updated, (err, result) => err ? rej(err) : res(result)));
    },

    async createOpinion(id, category) {
      const categories = require('../backend/opinion_categories');
      if (!categories.includes(category)) throw new Error('Invalid opinion category');
      const ssb = await openSsb();
      const userId = ssb.id;
      const report = await new Promise((res, rej) => ssb.get(id, (err, r) => err || !r || !r.content ? rej(new Error('Report not found')) : res(r)));
      const c = report.content;
      const list = Array.isArray(c.opinions_inhabitants) ? c.opinions_inhabitants : [];
      if (list.includes(userId)) throw new Error('Already opined');
      const opinions = Object.assign({}, c.opinions || {});
      opinions[category] = (opinions[category] || 0) + 1;
      const cat = normU(c.category);
      const updated = {
        ...c,
        type: 'report',
        replaces: id,
        opinions,
        opinions_inhabitants: list.concat(userId),
        updatedAt: new Date().toISOString(),
        status: normalizeStatus(c.status || 'OPEN'),
        category: cat,
        severity: normalizeSeverity(c.severity) || 'low',
        template: normalizeTemplate(cat, c.template || {})
      };
      const tombstone = { type: 'tombstone', target: id, deletedAt: new Date().toISOString(), author: userId };
      await new Promise((res, rej) => ssb.publish(tombstone, err => err ? rej(err) : res()));
      return new Promise((res, rej) => ssb.publish(updated, (err, result) => err ? rej(err) : res(result)));
    },

    async listAll() {
      const ssb = await openSsb();

      return new Promise((resolve, reject) => {
        pull(
          ssb.createLogStream({ limit: logLimit }),
          pull.collect((err, results) => {
            if (err) return reject(err);

            const tombstoned = buildValidatedTombstoneSet(results);
            const replaced = new Map();
            const reports = new Map();

            for (const r of results) {
              const key = r && r.key;
              const c = r && r.value && r.value.content ? r.value.content : null;
              if (!key || !c) continue;

              if (c.type === 'tombstone') continue;

              if (c.type === 'report') {
                if (c.replaces) replaced.set(c.replaces, key);

                const cat = normU(c.category);
                reports.set(key, {
                  id: key,
                  ...c,
                  category: cat,
                  status: normalizeStatus(c.status || 'OPEN'),
                  severity: normalizeSeverity(c.severity) || 'low',
                  confirmations: ensureArray(c.confirmations),
                  tags: ensureArray(c.tags),
                  template: normalizeTemplate(cat, c.template || {})
                });
              }
            }

            tombstoned.forEach(id => reports.delete(id));
            replaced.forEach((_, oldId) => reports.delete(oldId));

            resolve([...reports.values()]);
          })
        );
      });
    }
  };
};

