import crypto from 'node:crypto';

const REACTION_TYPES = new Set(['like', 'collect', 'share']);
const COUNTER_BY_TYPE = {
  like: 'like_count',
  collect: 'collect_count',
  share: 'share_count',
};

function plazaError(message, code, details) {
  return Object.assign(new Error(message), { code, details });
}

function clampLimit(value, fallback = 20, max = 50) {
  const limit = Number(value ?? fallback);
  if (!Number.isFinite(limit) || limit < 1) return fallback;
  return Math.min(Math.floor(limit), max);
}

function normalizeReactionType(value) {
  const type = String(value ?? '').trim();
  if (!REACTION_TYPES.has(type)) throw plazaError('不支持的互动类型', 'invalid_input');
  return type;
}

function formatCommentAuthor(row) {
  return {
    user_id: row.user_id,
    slug: row.author_slug || row.slug || row.username,
    display_name: row.author_display_name || row.display_name || row.username,
    avatar_url: '',
  };
}

export const interactionInternals = {
  clampLimit,
  normalizeReactionType,
};

export function createPlazaInteractionService(
  pool,
  { idFactory = () => crypto.randomUUID(), formatPostRow, plazaRedis = null } = {},
) {
  const requirePublishedPost = async (postId, conn = pool) => {
    const [rows] = await conn.query(
      `SELECT * FROM plaza_posts WHERE id = ? AND status = 'published' LIMIT 1`,
      [postId],
    );
    if (!rows[0]) throw plazaError('帖子不存在或已隐藏', 'POST_NOT_FOUND');
    return rows[0];
  };

  const loadViewerReactions = async (viewerId, postIds) => {
    const map = new Map();
    if (!viewerId || postIds.length === 0) return map;
    const placeholders = postIds.map(() => '?').join(', ');
    const [rows] = await pool.query(
      `SELECT post_id, type FROM plaza_reactions
       WHERE user_id = ? AND post_id IN (${placeholders})`,
      [viewerId, ...postIds],
    );
    for (const id of postIds) {
      map.set(id, { liked: false, collected: false });
    }
    for (const row of rows) {
      const current = map.get(row.post_id) ?? { liked: false, collected: false };
      if (row.type === 'like') current.liked = true;
      if (row.type === 'collect') current.collected = true;
      map.set(row.post_id, current);
    }
    return map;
  };

  const bumpCounter = async (conn, postId, field, delta) => {
    const now = Date.now();
    if (delta > 0) {
      await conn.query(
        `UPDATE plaza_posts SET ${field} = ${field} + ?, updated_at = ? WHERE id = ?`,
        [delta, now, postId],
      );
      return;
    }
    await conn.query(
      `UPDATE plaza_posts SET ${field} = GREATEST(CAST(${field} AS SIGNED) + ?, 0), updated_at = ? WHERE id = ?`,
      [delta, now, postId],
    );
  };

  const addReaction = async (userId, postId, typeInput) => {
    const type = normalizeReactionType(typeInput);
    const field = COUNTER_BY_TYPE[type];
    await requirePublishedPost(postId);
    const conn = await pool.getConnection();
    try {
      await conn.beginTransaction();
      const [existing] = await conn.query(
        `SELECT id FROM plaza_reactions WHERE post_id = ? AND user_id = ? AND type = ? LIMIT 1`,
        [postId, userId, type],
      );
      if (!existing[0]) {
        await conn.query(
          `INSERT INTO plaza_reactions (id, post_id, user_id, type, created_at) VALUES (?, ?, ?, ?, ?)`,
          [idFactory(), postId, userId, type, Date.now()],
        );
        await bumpCounter(conn, postId, field, 1);
      }
      await conn.commit();
    } catch (error) {
      await conn.rollback();
      throw error;
    } finally {
      conn.release();
    }
    return { post_id: postId, type, active: true };
  };

  const removeReaction = async (userId, postId, typeInput) => {
    const type = normalizeReactionType(typeInput);
    const field = COUNTER_BY_TYPE[type];
    const conn = await pool.getConnection();
    try {
      await conn.beginTransaction();
      const [result] = await conn.query(
        `DELETE FROM plaza_reactions WHERE post_id = ? AND user_id = ? AND type = ?`,
        [postId, userId, type],
      );
      if (result.affectedRows > 0) {
        await bumpCounter(conn, postId, field, -1);
      }
      await conn.commit();
    } catch (error) {
      await conn.rollback();
      throw error;
    } finally {
      conn.release();
    }
    return { post_id: postId, type, active: false };
  };

  const listComments = async (
    postId,
    { cursor = null, limit = 20, parentId = null, viewerId = null } = {},
  ) => {
    await requirePublishedPost(postId);
    const pageLimit = clampLimit(limit);
    const params = [postId, 'visible'];
    let parentClause = 'AND c.parent_id IS NULL';
    if (parentId) {
      parentClause = 'AND c.parent_id = ?';
      params.push(parentId);
    }
    let cursorClause = '';
    if (cursor) {
      cursorClause =
        'AND c.created_at < (SELECT created_at FROM plaza_comments WHERE id = ? LIMIT 1)';
      params.push(cursor);
    }
    params.push(pageLimit + 1);
    const [rows] = await pool.query(
      `SELECT c.*, u.slug AS author_slug, u.username, u.display_name AS author_display_name
       FROM plaza_comments c
       JOIN h5_users u ON u.id = c.user_id
       WHERE c.post_id = ? AND c.status = ? ${parentClause} ${cursorClause}
       ORDER BY c.created_at DESC
       LIMIT ?`,
      params,
    );
    const hasMore = rows.length > pageLimit;
    const pageRows = hasMore ? rows.slice(0, pageLimit) : rows;
    let likedSet = new Set();
    if (viewerId && pageRows.length > 0) {
      const ids = pageRows.map((row) => row.id);
      const [likedRows] = await pool.query(
        `SELECT comment_id FROM plaza_comment_reactions
         WHERE user_id = ? AND comment_id IN (${ids.map(() => '?').join(', ')})`,
        [viewerId, ...ids],
      );
      likedSet = new Set(likedRows.map((row) => row.comment_id));
    }
    const comments = pageRows.map((row) => ({
      id: row.id,
      content: row.content,
      author: formatCommentAuthor(row),
      like_count: Number(row.like_count ?? 0),
      reply_count: Number(row.reply_count ?? 0),
      viewer_liked: viewerId ? likedSet.has(row.id) : false,
      created_at: new Date(Number(row.created_at)).toISOString(),
      status: row.status,
      parent_id: row.parent_id,
    }));
    return {
      comments,
      next_cursor: hasMore ? pageRows[pageRows.length - 1].id : null,
      has_more: hasMore,
    };
  };

  const createComment = async (userId, postId, { content, parent_id: parentId = null }) => {
    const text = String(content ?? '').trim();
    if (!text) throw plazaError('评论不能为空', 'invalid_input');
    if (text.length > 500) throw plazaError('评论超出 500 字符', 'COMMENT_TOO_LONG');

    const post = await requirePublishedPost(postId);
    if (!post.allow_comment) throw plazaError('该帖子已关闭评论', 'COMMENT_DISABLED');

    const [users] = await pool.query(
      `SELECT plaza_comment_banned FROM h5_users WHERE id = ? LIMIT 1`,
      [userId],
    );
    if (users[0]?.plaza_comment_banned) {
      throw plazaError('无评论权限', 'POST_PERMISSION_DENIED');
    }

    if (plazaRedis?.checkCommentRateLimit) {
      const rate = await plazaRedis.checkCommentRateLimit(userId, postId);
      if (!rate.allowed) {
        throw plazaError('评论过于频繁，请稍后再试', 'COMMENT_RATE_LIMITED');
      }
    } else {
      const hourAgo = Date.now() - 3_600_000;
      const [recent] = await pool.query(
        `SELECT COUNT(*) AS count FROM plaza_comments
         WHERE user_id = ? AND post_id = ? AND created_at >= ? AND status = 'visible'`,
        [userId, postId, hourAgo],
      );
      if (Number(recent[0]?.count ?? 0) >= 10) {
        throw plazaError('评论过于频繁，请稍后再试', 'COMMENT_RATE_LIMITED');
      }
    }

    let parentComment = null;
    if (parentId) {
      const [parents] = await pool.query(
        `SELECT * FROM plaza_comments WHERE id = ? AND post_id = ? AND status = 'visible' LIMIT 1`,
        [parentId, postId],
      );
      parentComment = parents[0];
      if (!parentComment) throw plazaError('父评论不存在', 'COMMENT_NOT_FOUND');
      if (parentComment.parent_id) {
        throw plazaError('不允许三级嵌套回复', 'REPLY_DEPTH_EXCEEDED');
      }
    }

    const now = Date.now();
    const commentId = idFactory();
    const conn = await pool.getConnection();
    try {
      await conn.beginTransaction();
      await conn.query(
        `INSERT INTO plaza_comments
         (id, post_id, user_id, parent_id, content, status, created_at, updated_at)
         VALUES (?, ?, ?, ?, ?, 'visible', ?, ?)`,
        [commentId, postId, userId, parentId, text, now, now],
      );
      if (parentComment) {
        await conn.query(
          `UPDATE plaza_comments SET reply_count = reply_count + 1, updated_at = ? WHERE id = ?`,
          [now, parentId],
        );
      } else {
        await conn.query(
          `UPDATE plaza_posts SET comment_count = comment_count + 1, updated_at = ? WHERE id = ?`,
          [now, postId],
        );
      }
      await conn.commit();
    } catch (error) {
      await conn.rollback();
      throw error;
    } finally {
      conn.release();
    }
    await plazaRedis?.markCommentRateLimit?.(userId, postId);
    return { id: commentId };
  };

  const deleteComment = async (userId, commentId) => {
    const [rows] = await pool.query(
      `SELECT * FROM plaza_comments WHERE id = ? AND user_id = ? LIMIT 1`,
      [commentId, userId],
    );
    if (!rows[0]) throw plazaError('评论不存在', 'COMMENT_NOT_FOUND');
    const now = Date.now();
    await pool.query(
      `UPDATE plaza_comments
       SET content = '', status = 'deleted', deleted_by = 'user', updated_at = ?
       WHERE id = ?`,
      [now, commentId],
    );
    return { id: commentId, status: 'deleted' };
  };

  const toggleCommentLike = async (userId, commentId, liked) => {
    const [rows] = await pool.query(
      `SELECT c.* FROM plaza_comments c
       JOIN plaza_posts p ON p.id = c.post_id
       WHERE c.id = ? AND c.status = 'visible' AND p.status = 'published'
       LIMIT 1`,
      [commentId],
    );
    if (!rows[0]) throw plazaError('评论不存在', 'COMMENT_NOT_FOUND');

    const conn = await pool.getConnection();
    try {
      await conn.beginTransaction();
      if (liked) {
        const [existing] = await conn.query(
          `SELECT id FROM plaza_comment_reactions WHERE comment_id = ? AND user_id = ? LIMIT 1`,
          [commentId, userId],
        );
        if (!existing[0]) {
          await conn.query(
            `INSERT INTO plaza_comment_reactions (id, comment_id, user_id, created_at) VALUES (?, ?, ?, ?)`,
            [idFactory(), commentId, userId, Date.now()],
          );
          await conn.query(
            `UPDATE plaza_comments SET like_count = like_count + 1, updated_at = ? WHERE id = ?`,
            [Date.now(), commentId],
          );
        }
      } else {
        const [result] = await conn.query(
          `DELETE FROM plaza_comment_reactions WHERE comment_id = ? AND user_id = ?`,
          [commentId, userId],
        );
        if (result.affectedRows > 0) {
          await conn.query(
            `UPDATE plaza_comments SET like_count = GREATEST(CAST(like_count AS SIGNED) - 1, 0), updated_at = ? WHERE id = ?`,
            [Date.now(), commentId],
          );
        }
      }
      await conn.commit();
    } catch (error) {
      await conn.rollback();
      throw error;
    } finally {
      conn.release();
    }
    return { id: commentId, liked };
  };

  const resolveUserBySlug = async (slug) => {
    const [rows] = await pool.query(
      `SELECT id, slug, username, display_name,
              plaza_post_count, plaza_follower_count, plaza_following_count
       FROM h5_users WHERE slug = ? OR username = ? LIMIT 1`,
      [slug, slug],
    );
    const row = rows[0];
    if (!row) throw plazaError('用户不存在', 'user_not_found');
    return row;
  };

  const followUser = async (followerId, followeeSlug) => {
    const followee = await resolveUserBySlug(followeeSlug);
    if (followee.id === followerId) throw plazaError('不能关注自己', 'SELF_FOLLOW');
    const now = Date.now();
    const conn = await pool.getConnection();
    try {
      await conn.beginTransaction();
      const [result] = await conn.query(
        `INSERT IGNORE INTO plaza_follows (follower_id, followee_id, created_at) VALUES (?, ?, ?)`,
        [followerId, followee.id, now],
      );
      if (result.affectedRows > 0) {
        await conn.query(
          `UPDATE h5_users SET plaza_following_count = plaza_following_count + 1, updated_at = ? WHERE id = ?`,
          [now, followerId],
        );
        await conn.query(
          `UPDATE h5_users SET plaza_follower_count = plaza_follower_count + 1, updated_at = ? WHERE id = ?`,
          [now, followee.id],
        );
      }
      await conn.commit();
    } catch (error) {
      await conn.rollback();
      throw error;
    } finally {
      conn.release();
    }
    return { following: true };
  };

  const unfollowUser = async (followerId, followeeSlug) => {
    const followee = await resolveUserBySlug(followeeSlug);
    const now = Date.now();
    const conn = await pool.getConnection();
    try {
      await conn.beginTransaction();
      const [result] = await conn.query(
        `DELETE FROM plaza_follows WHERE follower_id = ? AND followee_id = ?`,
        [followerId, followee.id],
      );
      if (result.affectedRows > 0) {
        await conn.query(
          `UPDATE h5_users SET plaza_following_count = GREATEST(CAST(plaza_following_count AS SIGNED) - 1, 0), updated_at = ? WHERE id = ?`,
          [now, followerId],
        );
        await conn.query(
          `UPDATE h5_users SET plaza_follower_count = GREATEST(CAST(plaza_follower_count AS SIGNED) - 1, 0), updated_at = ? WHERE id = ?`,
          [now, followee.id],
        );
      }
      await conn.commit();
    } catch (error) {
      await conn.rollback();
      throw error;
    } finally {
      conn.release();
    }
    return { following: false };
  };

  const getUserProfile = async (slug, viewerId = null) => {
    const user = await resolveUserBySlug(slug);
    const [likeRows] = await pool.query(
      `SELECT COALESCE(SUM(like_count), 0) AS total_likes
       FROM plaza_posts WHERE user_id = ? AND status = 'published'`,
      [user.id],
    );
    const [recentRows] = await pool.query(
      `SELECT pp.*, c.name AS category_name, c.slug AS category_slug, c.icon AS category_icon
       FROM plaza_posts pp
       JOIN plaza_categories c ON c.id = pp.category_id
       WHERE pp.user_id = ? AND pp.status = 'published'
       ORDER BY pp.published_at DESC
       LIMIT 6`,
      [user.id],
    );
    let viewerFollowing = false;
    if (viewerId && viewerId !== user.id) {
      const [followRows] = await pool.query(
        `SELECT 1 FROM plaza_follows WHERE follower_id = ? AND followee_id = ? LIMIT 1`,
        [viewerId, user.id],
      );
      viewerFollowing = followRows.length > 0;
    }
    const recentPosts = recentRows.map((row) =>
      formatPostRow ? formatPostRow(row, { viewerReacted: null }) : row,
    );
    return {
      user: {
        user_id: user.id,
        slug: user.slug || user.username,
        display_name: user.display_name || user.username,
        avatar_url: '',
        bio: '',
        stats: {
          post_count: Number(user.plaza_post_count ?? 0),
          follower_count: Number(user.plaza_follower_count ?? 0),
          following_count: Number(user.plaza_following_count ?? 0),
          total_likes: Number(likeRows[0]?.total_likes ?? 0),
        },
        viewer_following: viewerId ? viewerFollowing : undefined,
      },
      recent_posts: recentPosts,
    };
  };

  const listUserPosts = async (slug, { cursor = null, limit = 20, viewerId = null } = {}) => {
    const user = await resolveUserBySlug(slug);
    const pageLimit = clampLimit(limit);
    const params = [user.id, 'published'];
    let cursorClause = '';
    if (cursor) {
      cursorClause =
        'AND (pp.published_at < (SELECT published_at FROM plaza_posts WHERE id = ? LIMIT 1) OR (pp.published_at = (SELECT published_at FROM plaza_posts WHERE id = ? LIMIT 1) AND pp.id < ?))';
      params.push(cursor, cursor, cursor);
    }
    params.push(pageLimit + 1);
    const [rows] = await pool.query(
      `SELECT pp.*, c.name AS category_name, c.slug AS category_slug, c.icon AS category_icon
       FROM plaza_posts pp
       JOIN plaza_categories c ON c.id = pp.category_id
       WHERE pp.user_id = ? AND pp.status = ? ${cursorClause}
       ORDER BY pp.published_at DESC, pp.id DESC
       LIMIT ?`,
      params,
    );
    const hasMore = rows.length > pageLimit;
    const pageRows = hasMore ? rows.slice(0, pageLimit) : rows;
    const reactions = await loadViewerReactions(
      viewerId,
      pageRows.map((row) => row.id),
    );
    const posts = pageRows.map((row) =>
      formatPostRow
        ? formatPostRow(row, { viewerReacted: viewerId ? reactions.get(row.id) ?? null : null })
        : row,
    );
    return {
      posts,
      next_cursor: hasMore ? pageRows[pageRows.length - 1].id : null,
      has_more: hasMore,
    };
  };

  return {
    loadViewerReactions,
    addReaction,
    removeReaction,
    listComments,
    createComment,
    deleteComment,
    toggleCommentLike,
    followUser,
    unfollowUser,
    getUserProfile,
    listUserPosts,
  };
}
