const pull = require('../server/node_modules/pull-stream');
const ssbClientGUI = require("../client/gui");
const coolerInstance = ssbClientGUI({ offline: require('../server/ssb_config').offline });
const models = require("../models/main_models");
const { about, friend } = models({
  cooler: coolerInstance,
  isPublic: require('../server/ssb_config').public,
});
const { getConfig } = require('../configs/config-manager.js');
const logLimit = getConfig().ssbLogStream?.limit || 1000;

function toImageUrl(imgId, size=256){
  if (!imgId) return '/assets/images/default-avatar.png';
  if (typeof imgId === 'string' && imgId.startsWith('/image/')) return imgId.replace('/image/256/','/image/'+size+'/').replace('/image/512/','/image/'+size+'/');
  return `/image/${size}/${encodeURIComponent(imgId)}`;
}

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

  async function getLastKarmaScore(feedId) {
    const ssbClient = await openSsb();
    return new Promise(resolve => {
      const src = ssbClient.messagesByType
        ? ssbClient.messagesByType({ type: "karmaScore", reverse: true })
        : ssbClient.createLogStream && ssbClient.createLogStream({ reverse: true });
      if (!src) return resolve(0);
      pull(
        src,
        pull.filter(msg => {
          const v = msg.value || msg;
          const c = v.content || {};
          return v.author === feedId && c.type === "karmaScore" && typeof c.karmaScore !== "undefined";
        }),
        pull.take(1),
        pull.collect((err, arr) => {
          if (err || !arr || !arr.length) return resolve(0);
          const v = arr[0].value || arr[0];
          resolve(v.content.karmaScore || 0);
        })
      );
    });
  }

  async function getLastActivityTimestamp(feedId) {
    const ssbClient = await openSsb();
    const norm = (t) => (t && t < 1e12 ? t * 1000 : t || 0);
    return new Promise((resolve) => {
      pull(
        ssbClient.createUserStream({ id: feedId, reverse: true }),
        pull.filter(m => m && m.value && m.value.content && m.value.content.type !== 'tombstone'),
        pull.take(1),
        pull.collect((err, arr) => {
          if (err || !arr || !arr.length) return resolve(null);
          const m = arr[0];
          const ts = norm((m.value && m.value.timestamp) || m.timestamp);
          resolve(ts || null);
        })
      );
    });
  }

  function bucketLastActivity(ts) {
    if (!ts) return { bucket: 'red', range: '≥6m' };
    const now = Date.now();
    const delta = Math.max(0, now - ts);
    const days = delta / 86400000;
    if (days < 14) return { bucket: 'green', range: '<2w' };
    if (days < 182.5) return { bucket: 'orange', range: '2w–6m' };
    return { bucket: 'red', range: '≥6m' };
  }

  const timeoutPromise = (timeout) => new Promise((_, reject) => setTimeout(() => reject('Timeout'), timeout));
  const fetchUserImageUrl = async (feedId, size=256) => {
    try{
      const img = await Promise.race([about.image(feedId), timeoutPromise(5000)]);
      const id = typeof img === 'string' ? img : (img && (img.link || img.url));
      return toImageUrl(id, size);
    }catch{
      return '/assets/images/default-avatar.png';
    }
  };

  async function listAllBase(ssbClient) {
    const authorsMsgs = await new Promise((res, rej) => {
      pull(
        ssbClient.createLogStream({ limit: logLimit, reverse: true }),
        pull.filter(msg => !!msg.value?.author && msg.value?.content?.type !== 'tombstone'),
        pull.collect((err, msgs) => err ? rej(err) : res(msgs))
      );
    });
    const uniqueFeedIds = Array.from(new Set(authorsMsgs.map(r => r.value.author).filter(Boolean)));
    const users = await Promise.all(
      uniqueFeedIds.map(async (feedId) => {
        const rawName = await about.name(feedId);
        const name = rawName || feedId.slice(0, 10);
        const description = await about.description(feedId);
        const photo = await fetchUserImageUrl(feedId, 256);
        const lastActivityTs = await getLastActivityTimestamp(feedId);
        const { bucket, range } = bucketLastActivity(lastActivityTs);
        return { id: feedId, name, description, photo, lastActivityTs, lastActivityBucket: bucket, lastActivityRange: range };
      })
    );
    return Array.from(new Map(users.filter(u => u && u.id).map(u => [u.id, u])).values());
  }

  function normalizeRel(rel) {
    const r = rel || {};
    const iFollow = !!(r.following || r.iFollow || r.youFollow || r.i_follow || r.isFollowing);
    const followsMe = !!(r.followsMe || r.followingMe || r.follows_me || r.theyFollow || r.isFollowedBy);
    const blocking = !!(r.blocking || r.iBlock || r.isBlocking);
    const blockedBy = !!(r.blocked || r.blocksMe || r.isBlockedBy);
    return { iFollow, followsMe, blocking, blockedBy };
  }

  return {
    async listInhabitants(options = {}) {
      const { filter = 'all', search = '', location = '', language = '', skills = '', includeInactive = false } = options;
      const ssbClient = await openSsb();
      const userId = ssbClient.id;

      const filterInactive = (users) => {
        if (includeInactive) return users;
        return users.filter(u => u.lastActivityBucket !== 'red');
      };

      if (filter === 'GALLERY') {
        const users = await listAllBase(ssbClient);
        return filterInactive(users);
      }

      if (filter === 'all' || filter === 'TOP KARMA' || filter === 'TOP ACTIVITY' || filter === 'TOP INACTIVITY' || filter === 'TOP ECO') {
        let users = await listAllBase(ssbClient);
        if (filter !== 'TOP ACTIVITY' && filter !== 'TOP INACTIVITY') {
          users = filterInactive(users);
        }
        if (search) {
          const q = search.toLowerCase();
          users = users.filter(u =>
            (u.name || '').toLowerCase().includes(q) ||
            (u.description || '').toLowerCase().includes(q) ||
            (u.id || '').toLowerCase().includes(q)
          );
        }
        const bytesByAuthor = await new Promise((res) => {
          pull(
            ssbClient.createLogStream({ limit: logLimit }),
            pull.collect((err, msgs) => {
              if (err || !Array.isArray(msgs)) return res({});
              const acc = {};
              for (const m of msgs) {
                const author = m && m.value && m.value.author;
                if (!author) continue;
                try { acc[author] = (acc[author] || 0) + Buffer.byteLength(JSON.stringify(m.value), 'utf8'); } catch (_) {}
              }
              res(acc);
            })
          );
        });
        const withMetrics = await Promise.all(users.map(async u => {
          const karmaScore = await getLastKarmaScore(u.id);
          const bytes = (bytesByAuthor && bytesByAuthor[u.id]) || 0;
          const carbonGrams = (bytes / (1024 * 1024)) * 0.095;
          if (filter === 'TOP ECO') {
            const ecoScore = karmaScore / Math.max(0.01, carbonGrams);
            return { ...u, karmaScore, carbonGrams, ecoScore };
          }
          return { ...u, karmaScore, carbonGrams };
        }));
        if (filter === 'TOP KARMA') return withMetrics.sort((a, b) => (b.karmaScore || 0) - (a.karmaScore || 0));
        if (filter === 'TOP ACTIVITY') return withMetrics.sort((a, b) => (b.lastActivityTs || 0) - (a.lastActivityTs || 0));
        if (filter === 'TOP INACTIVITY') return withMetrics.sort((a, b) => (a.lastActivityTs || 0) - (b.lastActivityTs || 0));
        if (filter === 'TOP ECO') return withMetrics.sort((a, b) => (b.ecoScore || 0) - (a.ecoScore || 0));
        return withMetrics;
      }

      if (filter === 'contacts') {
        const all = await this.listInhabitants({ filter: 'all' });
        const result = [];
        for (const user of all) {
          const rel = await friend.getRelationship(user.id).catch(() => ({}));
          if (rel && (rel.following || rel.iFollow)) result.push(user);
        }
        return Array.from(new Map(result.map(u => [u.id, u])).values());
      }

      if (filter === 'blocked') {
        const all = await this.listInhabitants({ filter: 'all', includeInactive: true });
        const result = [];
        for (const user of all) {
          const rel = await friend.getRelationship(user.id).catch(() => ({}));
          const n = normalizeRel(rel);
          if (n.blocking) result.push({ ...user, isBlocked: true });
        }
        return Array.from(new Map(result.map(u => [u.id, u])).values());
      }

      if (filter === 'SUGGESTED') {
        const base = await listAllBase(ssbClient);
        const active = filterInactive(base);
        const cvRecords = await new Promise((res) => {
          pull(
            ssbClient.createLogStream({ limit: logLimit, reverse: true }),
            pull.filter(msg => msg && msg.value && msg.value.content && msg.value.content.type === 'curriculum'),
            pull.collect((err, msgs) => err ? res([]) : res(msgs))
          );
        });
        const cvByAuthor = new Map();
        for (const r of cvRecords) {
          const c = r.value && r.value.content;
          if (c && c.author && !cvByAuthor.has(c.author)) cvByAuthor.set(c.author, c);
        }
        const extractSkills = (cv) => cv ? [
          ...(cv.personalSkills || []),
          ...(cv.oasisSkills || []),
          ...(cv.educationalSkills || []),
          ...(cv.professionalSkills || [])
        ].map(s => String(s || '').toLowerCase()).filter(Boolean) : [];
        const mecv = await this.getCVByUserId().catch(() => null);
        const mySkills = extractSkills(mecv);
        const rels = await Promise.all(
          active.map(async u => {
            if (u.id === userId) return null;
            const rel = await friend.getRelationship(u.id).catch(() => ({}));
            const n = normalizeRel(rel);
            if (n.iFollow || n.blocking || n.blockedBy) return null;
            const karmaScore = await getLastKarmaScore(u.id);
            const theirSkills = extractSkills(cvByAuthor.get(u.id));
            const commonSkills = mySkills.length && theirSkills.length
              ? Array.from(new Set(mySkills.filter(s => theirSkills.includes(s))))
              : [];
            const followsMeBonus = n.followsMe ? 20 : 0;
            const karmaBonus = Math.min(20, Math.log10(1 + Math.max(0, karmaScore)) * 5);
            const skillBonus = commonSkills.length * 4;
            const activityBonus = u.lastActivityBucket === 'green' ? 5 : (u.lastActivityBucket === 'orange' ? 2 : 0);
            const suggestionScore = followsMeBonus + karmaBonus + skillBonus + activityBonus;
            return { user: u, rel: n, karmaScore, commonSkills, suggestionScore };
          })
        );
        const candidates = rels.filter(Boolean).filter(x => x.suggestionScore > 0);
        const enriched = candidates.map(x => ({
          ...x.user,
          karmaScore: x.karmaScore,
          followsYou: x.rel.followsMe,
          commonSkills: x.commonSkills,
          mutualCount: x.rel.followsMe ? 1 : 0,
          suggestionScore: x.suggestionScore
        }));
        const unique = Array.from(new Map(enriched.map(u => [u.id, u])).values());
        return unique.sort((a, b) =>
          (b.suggestionScore || 0) - (a.suggestionScore || 0) ||
          (b.karmaScore || 0) - (a.karmaScore || 0) ||
          (b.lastActivityTs || 0) - (a.lastActivityTs || 0)
        );
      }

      if (filter === 'CVs') {
        const records = await new Promise((res, rej) => {
          pull(
            ssbClient.createLogStream({ limit: logLimit, reverse: true}),
            pull.filter(msg =>
              msg.value.content?.type === 'curriculum' &&
              msg.value.content?.type !== 'tombstone'
            ),
            pull.collect((err, msgs) => err ? rej(err) : res(msgs))
          );
        });

        let cvs = records.map(r => r.value.content);
        cvs = Array.from(new Map(cvs.map(u => [u.author, u])).values());

        if (filter === 'CVs') {
          let out = await Promise.all(cvs.map(async c => {
            const photo = await fetchUserImageUrl(c.author, 256);
            const lastActivityTs = await getLastActivityTimestamp(c.author);
            const { bucket, range } = bucketLastActivity(lastActivityTs);
            const base = this._normalizeCurriculum(c, photo);
            return { ...base, lastActivityTs, lastActivityBucket: bucket, lastActivityRange: range };
          }));
          out = filterInactive(out);
          if (search) {
            const q = search.toLowerCase();
            out = out.filter(u =>
              (u.name || '').toLowerCase().includes(q) ||
              (u.description || '').toLowerCase().includes(q) ||
              u.skills.some(s => (s || '').toLowerCase().includes(q))
            );
          }
          if (location) out = out.filter(u => (u.location || '').toLowerCase() === location.toLowerCase());
          if (language) out = out.filter(u => u.languages.map(l => l.toLowerCase()).includes(language.toLowerCase()));
          if (skills) {
            const skillList = skills.split(',').map(s => s.trim().toLowerCase()).filter(Boolean);
            out = out.filter(u => skillList.every(s => u.skills.map(k => (k || '').toLowerCase()).includes(s)));
          }
          return out;
        }

      }

      return [];
    },

    _normalizeCurriculum(c, photoUrl) {
      const photo = c.photo ? toImageUrl(c.photo, 256) : (photoUrl || toImageUrl(null, 256));
      return {
        id: c.author,
        name: c.name,
        description: c.description,
        photo,
        skills: [
          ...(c.personalSkills || []),
          ...(c.oasisSkills || []),
          ...(c.educationalSkills || []),
          ...(c.professionalSkills || [])
        ],
        location: c.location,
        languages: typeof c.languages === 'string'
          ? c.languages.split(',').map(x => x.trim())
          : Array.isArray(c.languages) ? c.languages : [],
        status: c.status,
        preferences: c.preferences,
        createdAt: c.createdAt
      };
    },

    async getLatestAboutById(id) {
      const ssbClient = await openSsb();
      const records = await new Promise((res, rej) => {
        pull(
          ssbClient.createUserStream({ id }),
          pull.filter(msg =>
            msg.value.content?.type === 'about' &&
            msg.value.content?.type !== 'tombstone'
          ),
          pull.collect((err, msgs) => err ? rej(err) : res(msgs))
        );
      });
      if (!records.length) return null;
      const latest = records.sort((a, b) => b.value.timestamp - a.value.timestamp)[0];
      return latest.value.content;
    },

    async getFeedByUserId(id) {
      const ssbClient = await openSsb();
      const targetId = id || ssbClient.id;
      const records = await new Promise((res, rej) => {
        pull(
          ssbClient.createUserStream({ id: targetId }),
          pull.filter(msg =>
            msg.value &&
            msg.value.content &&
            typeof msg.value.content.text === 'string' &&
            msg.value.content?.type !== 'tombstone'
          ),
          pull.collect((err, msgs) => err ? rej(err) : res(msgs))
        );
      });
      return records
        .filter(m => typeof m.value.content.text === 'string')
        .sort((a, b) => b.value.timestamp - a.value.timestamp)
        .slice(0, 10);
    },

    async getInhabitantStats(targetId, viewerId) {
      const ssbClient = await openSsb();
      const target = targetId || ssbClient.id;
      const viewer = viewerId || ssbClient.id;
      const isOwner = viewer === target;
      const arr = (v) => Array.isArray(v) ? v : [];
      const up = (v) => String(v || '').toUpperCase();
      const COUNTED = new Set(['post','event','task','forum','tribe','market','job','housing','project','industry','shop','image','video','audio','document','bookmark','transfer','map']);
      const accessible = (type, c) => {
        if (c.encryptedPayload) return false;
        switch (type) {
          case 'task':   return up(c.isPublic) !== 'PRIVATE' || isOwner || arr(c.assignees).includes(viewer);
          case 'event':  return String(c.isPublic || '').toLowerCase() !== 'private' || isOwner || arr(c.attendees).includes(viewer);
          case 'forum':  return c.isPrivate !== true || isOwner;
          case 'job':    return up(c.visibility) !== 'HIDDEN' || isOwner || arr(c.subscribers).includes(viewer);
          case 'housing': return up(c.visibility) !== 'HIDDEN' || isOwner;
          case 'market': return up(c.visibility) !== 'HIDDEN' || isOwner;
          case 'shop':   return up(c.visibility) !== 'CLOSED' || isOwner;
          case 'tribe':  { const st = up(c.status); return !(st === 'PRIVATE' || st === 'INVITE-ONLY') || isOwner || arr(c.members).includes(viewer); }
          default: return true;
        }
      };
      const counts = {};
      await new Promise((resolve) => {
        pull(
          ssbClient.createUserStream({ id: target }),
          pull.drain((m) => {
            const c = m && m.value && m.value.content;
            if (!c || typeof c !== 'object') return;
            const type = c.type;
            if (!type || !COUNTED.has(type)) return;
            if (c.replaces) return;
            if (!accessible(type, c)) return;
            counts[type] = (counts[type] || 0) + 1;
          }, () => resolve())
        );
      });
      return counts;
    },

    async getCVByUserId(id) {
      const ssbClient = await openSsb();
      const targetId = id || ssbClient.id;
      const records = await new Promise((res, rej) => {
        pull(
          ssbClient.createUserStream({ id: targetId }),
          pull.filter(msg =>
            msg.value.content?.type === 'curriculum' &&
            msg.value.content?.type !== 'tombstone'
          ),
          pull.collect((err, msgs) => err ? rej(err) : res(msgs))
        );
      });
      return records.length ? records[records.length - 1].value.content : null;
    },

    async getCandidatesForJob(job, viewerId = null) {
      if (!job || typeof job !== 'object') return [];
      const ssbClient = await openSsb();
      const tokenize = (s) => String(s || '')
        .toLowerCase()
        .split(/[^a-z0-9áéíóúñü+#./-]+/i)
        .map(t => t.trim())
        .filter(t => t && t.length >= 2);
      const stop = new Set(['the','a','an','and','or','of','to','in','for','on','with','is','are','be','as','at','by','from','that','this','it','we','you','our','your','un','una','el','la','los','las','de','del','en','con','para','por','y','o','un','una','que','se','su','sus','al','etc']);
      const keywords = new Set();
      const addAll = (arr) => arr.forEach(t => { if (!stop.has(t)) keywords.add(t); });
      if (Array.isArray(job.tags)) job.tags.forEach(t => { const k = String(t || '').toLowerCase().trim(); if (k) keywords.add(k); });
      addAll(tokenize(job.title));
      addAll(tokenize(job.description));
      addAll(tokenize(job.requirements));
      if (keywords.size === 0) return [];

      const records = await new Promise((res, rej) => {
        pull(
          ssbClient.createLogStream({ limit: logLimit, reverse: true }),
          pull.filter(msg =>
            msg.value?.content?.type === 'curriculum' &&
            msg.value?.content?.type !== 'tombstone'
          ),
          pull.collect((err, msgs) => err ? rej(err) : res(msgs))
        );
      });
      let cvs = records.map(r => r.value.content);
      cvs = Array.from(new Map(cvs.map(u => [u.author, u])).values());
      cvs = cvs.filter(c => String(c.visibility || 'PUBLIC').toUpperCase() !== 'HIDDEN');

      const jobAuthor = job.author || null;
      const out = await Promise.all(cvs.map(async c => {
        if (!c.author) return null;
        if (jobAuthor && c.author === jobAuthor) return null;
        const cvSkills = [
          ...(c.personalSkills || []),
          ...(c.oasisSkills || []),
          ...(c.educationalSkills || []),
          ...(c.professionalSkills || [])
        ].map(s => String(s || '').toLowerCase()).filter(Boolean);
        const common = Array.from(new Set(cvSkills.filter(s => keywords.has(s))));
        if (common.length === 0) return null;
        if (viewerId && c.author !== viewerId) {
          try {
            const rel = await friend.getRelationship(c.author);
            if (rel && (rel.blocking || rel.blockedBy)) return null;
          } catch (_) {}
        }
        const authorTs = await getLastActivityTimestamp(c.author);
        let interactionTs = null;
        try {
          const cvId = c.id || c.key || null;
          if (cvId) {
            const ssbClient = await openSsb();
            interactionTs = await new Promise((resolve) => {
              try {
                pull(
                  ssbClient.backlinks.read({ query: [{ $filter: { dest: cvId } }], index: 'DTA', reverse: true, limit: 1 }),
                  pull.collect((err, arr) => {
                    if (err || !arr || !arr.length) return resolve(null);
                    const m = arr[0];
                    const raw = (m.value && m.value.timestamp) || m.timestamp;
                    resolve(raw && raw < 1e12 ? raw * 1000 : raw || null);
                  })
                );
              } catch (_) { resolve(null); }
            });
          }
        } catch (_) {}
        const lastActivityTs = Math.max(authorTs || 0, interactionTs || 0) || null;
        const { bucket, range } = bucketLastActivity(lastActivityTs);
        if (bucket === 'red') return null;
        const photo = await fetchUserImageUrl(c.author, 256);
        const matchScore = common.length / keywords.size;
        return {
          id: c.author,
          name: c.name || 'Anonymous',
          description: c.description || '',
          photo,
          location: c.location || '',
          status: c.status || '',
          preferences: c.preferences || '',
          languages: typeof c.languages === 'string'
            ? c.languages.split(',').map(x => x.trim()).filter(Boolean)
            : Array.isArray(c.languages) ? c.languages : [],
          commonSkills: common,
          matchScore,
          lastActivityTs,
          lastActivityBucket: bucket,
          lastActivityRange: range
        };
      }));
      return out.filter(Boolean).sort((a, b) => {
        if (b.matchScore !== a.matchScore) return b.matchScore - a.matchScore;
        return (b.lastActivityTs || 0) - (a.lastActivityTs || 0);
      }).slice(0, 20);
    },

    async getPhotoUrlByUserId(id, size = 256) {
      return await fetchUserImageUrl(id, size);
    },

    async getLastActivityTimestampByUserId(id) {
      return await getLastActivityTimestamp(id);
    },

    bucketLastActivity(ts) {
      return bucketLastActivity(ts);
    }
  };
};

