import crypto from 'node:crypto';

const SYNC_SET = 'plaza:sync:post_ids';
const FEED_GEN_KEY = 'plaza:feed:gen';

function hashIp(ip) {
  return crypto.createHash('sha256').update(String(ip ?? 'unknown')).digest('hex').slice(0, 16);
}

function counterKey(postId, field) {
  return `plaza:post:${postId}:${field}`;
}

function viewDedupKey(postId, ipHash) {
  return `plaza:view:${ipHash}:${postId}`;
}

function feedCacheKey(gen, sort, categorySlug, cursor) {
  const category = categorySlug || 'all';
  const page = cursor || 'root';
  return `plaza:feed:${sort}:${category}:cursor:${page}:g${gen}`;
}

export function createNoopPlazaRedis(pool = null) {
  const viewDedup = new Map();

  return {
    enabled: false,
    async connect() {},
    async disconnect() {},
    async recordView(postId, ip) {
      if (!pool) return { counted: false };
      const ipHash = hashIp(ip);
      const dedupKey = `${ipHash}:${postId}`;
      if (viewDedup.has(dedupKey)) return { counted: false };
      viewDedup.set(dedupKey, Date.now());
      setTimeout(() => viewDedup.delete(dedupKey), 86_400_000).unref?.();
      const now = Date.now();
      const [result] = await pool.query(
        `UPDATE plaza_posts SET view_count = view_count + 1, updated_at = ?
         WHERE id = ? AND status = 'published'`,
        [now, postId],
      );
      return { counted: (result.affectedRows ?? 0) > 0 };
    },
    async getFeedCache() {
      return null;
    },
    async setFeedCache() {},
    async invalidateFeedCaches() {},
    async syncCountersToMySQL() {
      return { synced: 0 };
    },
    async checkCommentRateLimit() {
      return { allowed: true };
    },
    async markCommentRateLimit() {},
  };
}

export async function createPlazaRedis(redisUrl, pool) {
  if (!redisUrl) return createNoopPlazaRedis(pool);

  const { createClient } = await import('redis');
  const client = createClient({ url: redisUrl });
  client.on('error', (error) => {
    console.error('Plaza Redis error:', error.message);
  });
  await client.connect();

  const getFeedGen = async () => Number((await client.get(FEED_GEN_KEY)) ?? 0);

  return {
    enabled: true,
    client,
    async disconnect() {
      if (client.isOpen) await client.disconnect();
    },
    async recordView(postId, ip) {
      const ipHash = hashIp(ip);
      const dedup = viewDedupKey(postId, ipHash);
      const inserted = await client.set(dedup, '1', { NX: true, EX: 86_400 });
      if (!inserted) return { counted: false };
      await client.incr(counterKey(postId, 'view_count'));
      await client.sAdd(SYNC_SET, postId);
      return { counted: true };
    },
    async getFeedCache(sort, categorySlug, cursor) {
      const gen = await getFeedGen();
      const key = feedCacheKey(gen, sort, categorySlug, cursor);
      const raw = await client.get(key);
      if (!raw) return null;
      try {
        return JSON.parse(raw);
      } catch {
        return null;
      }
    },
    async setFeedCache(sort, categorySlug, cursor, payload, ttlSeconds = 300) {
      const gen = await getFeedGen();
      const key = feedCacheKey(gen, sort, categorySlug, cursor);
      await client.set(key, JSON.stringify(payload), { EX: ttlSeconds });
    },
    async invalidateFeedCaches() {
      await client.incr(FEED_GEN_KEY);
    },
    async syncCountersToMySQL() {
      const postIds = await client.sMembers(SYNC_SET);
      if (postIds.length === 0) return { synced: 0 };

      let synced = 0;
      const now = Date.now();
      for (const postId of postIds) {
        const delta = Number((await client.get(counterKey(postId, 'view_count'))) ?? 0);
        if (delta <= 0) {
          await client.sRem(SYNC_SET, postId);
          continue;
        }
        await pool.query(
          `UPDATE plaza_posts SET view_count = view_count + ?, updated_at = ? WHERE id = ?`,
          [delta, now, postId],
        );
        await client.decrBy(counterKey(postId, 'view_count'), delta);
        const remaining = Number((await client.get(counterKey(postId, 'view_count'))) ?? 0);
        if (remaining <= 0) {
          await client.del(counterKey(postId, 'view_count'));
          await client.sRem(SYNC_SET, postId);
        }
        synced += 1;
      }
      return { synced };
    },
    async checkCommentRateLimit(userId, postId, maxPerHour = 10) {
      const key = `plaza:comment_rate:${userId}:${postId}`;
      const count = Number((await client.get(key)) ?? 0);
      return { allowed: count < maxPerHour, count };
    },
    async markCommentRateLimit(userId, postId) {
      const key = `plaza:comment_rate:${userId}:${postId}`;
      const count = await client.incr(key);
      if (count === 1) await client.expire(key, 3600);
      return count;
    },
  };
}
