import crypto from 'node:crypto';
import { computeHotScore } from './plaza-algorithm.mjs';

const DEFAULT_CATEGORIES = [
  { name: '职场报告', slug: 'work-report', icon: '📊', sort_order: 1 },
  { name: '学习笔记', slug: 'study-notes', icon: '📚', sort_order: 2 },
  { name: '创意作品', slug: 'creative', icon: '🎨', sort_order: 3 },
  { name: '门店展示', slug: 'business', icon: '🏪', sort_order: 4 },
  { name: '旅行攻略', slug: 'travel', icon: '✈️', sort_order: 5 },
  { name: '数据分析', slug: 'data-analysis', icon: '📈', sort_order: 6 },
  { name: '生活记录', slug: 'lifestyle', icon: '🌿', sort_order: 7 },
  { name: '其他', slug: 'other', icon: '💡', sort_order: 99 },
];

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

function normalizeTags(value) {
  if (!Array.isArray(value)) return [];
  const tags = value
    .map((item) => String(item ?? '').trim())
    .filter(Boolean)
    .slice(0, 5);
  return tags;
}

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 normalizeSort(value) {
  const raw = String(value ?? 'recommend').trim().toLowerCase();
  if (raw === 'new') return 'new';
  if (raw === 'hot') return 'hot';
  return 'recommend';
}

export function defaultPlazaCoverUrl(publicUrl) {
  const value = String(publicUrl ?? '').trim();
  if (!value) return '';
  const [pathname, suffix = ''] = value.split(/([?#].*)/, 2);
  if (!pathname) return '';
  const nextPath = pathname.endsWith('/') ? `${pathname}index.thumbnail.png` : `${pathname}.thumbnail.png`;
  return `${nextPath}${suffix}`;
}

export function resolvePlazaCoverUrl(inputCoverUrl, publicUrl) {
  const value = String(inputCoverUrl ?? '').trim();
  if (!value) return defaultPlazaCoverUrl(publicUrl);
  if (/^https?:\/\//i.test(value)) return value;
  const base = String(publicUrl ?? '').trim();
  if (base && /^https?:\/\//i.test(base)) {
    try {
      return new URL(value, base).toString();
    } catch {
      return value;
    }
  }
  return value;
}

function formatStats(row) {
  return {
    view_count: Number(row.view_count ?? 0),
    like_count: Number(row.like_count ?? 0),
    collect_count: Number(row.collect_count ?? 0),
    comment_count: Number(row.comment_count ?? 0),
    share_count: Number(row.share_count ?? 0),
  };
}

const FRESH_POST_WINDOW_MS = 24 * 60 * 60 * 1000;

function promoteFreshPosts(items, now = Date.now()) {
  const fresh = [];
  const stable = [];
  for (const post of items) {
    const publishedAt = Date.parse(post.published_at);
    if (Number.isFinite(publishedAt) && now - publishedAt <= FRESH_POST_WINDOW_MS) {
      fresh.push(post);
    } else {
      stable.push(post);
    }
  }
  fresh.sort((a, b) => Date.parse(b.published_at) - Date.parse(a.published_at));
  return [...fresh, ...stable];
}

function formatPostRow(row, { category, viewerReacted = null, publicationUrl = null } = {}) {
  const resolvedPublicationUrl = publicationUrl ?? row.public_url ?? null;
  return {
    id: row.id,
    title: row.title,
    summary: row.summary ?? '',
    cover_url: resolvePlazaCoverUrl(row.cover_url, resolvedPublicationUrl),
    category: category ?? {
      id: row.category_id,
      name: row.category_name ?? '',
      slug: row.category_slug ?? '',
      icon: row.category_icon ?? '',
    },
    tags: Array.isArray(row.tags) ? row.tags : JSON.parse(row.tags ?? '[]'),
    author: {
      user_id: row.user_id,
      slug: row.user_slug,
      display_name: row.user_display_name,
      avatar_url: row.user_avatar_url ?? '',
    },
    stats: formatStats(row),
    viewer_reacted: viewerReacted,
    published_at: new Date(Number(row.published_at)).toISOString(),
    ...(resolvedPublicationUrl ? { publication_url: resolvedPublicationUrl } : {}),
    ...(row.allow_comment != null ? { allow_comment: Boolean(row.allow_comment) } : {}),
    ...(row.status ? { status: row.status } : {}),
  };
}

export const plazaInternals = {
  normalizeTags,
  clampLimit,
  normalizeSort,
  defaultPlazaCoverUrl,
  resolvePlazaCoverUrl,
};

export function createPlazaPostService(
  pool,
  {
    idFactory = () => crypto.randomUUID(),
    loadViewerReactions = null,
    plazaRedis = null,
    algorithmConfig = null,
    onPostPublished = null,
    loadFeaturedPosts = null,
    recommendService = null,
  } = {},
) {
  const autoApprove = String(process.env.PLAZA_AUTO_APPROVE ?? '').toLowerCase() === 'true';

  const resolveHotScore = async (publishedAt) => {
    const config = algorithmConfig ?? {
      w_view: 0.1,
      w_like: 3.0,
      w_comment: 5.0,
      w_collect: 4.0,
      decay_base: 2.0,
      decay_exp: 1.5,
    };
    return computeHotScore(
      {
        view_count: 0,
        like_count: 0,
        comment_count: 0,
        collect_count: 0,
        published_at: publishedAt,
      },
      config,
    );
  };

  const ensureCategories = async () => {
    const [rows] = await pool.query(`SELECT COUNT(*) AS count FROM plaza_categories`);
    if (Number(rows[0]?.count ?? 0) > 0) return;
    const now = Date.now();
    for (const category of DEFAULT_CATEGORIES) {
      await pool.query(
        `INSERT INTO plaza_categories
         (id, name, slug, icon, description, sort_order, is_active, created_at)
         VALUES (?, ?, ?, ?, '', ?, 1, ?)`,
        [idFactory(), category.name, category.slug, category.icon, category.sort_order, now],
      );
    }
  };

  const listCategories = async () => {
    await ensureCategories();
    const [rows] = await pool.query(
      `SELECT c.id, c.name, c.slug, c.icon,
              COUNT(p.id) AS post_count
       FROM plaza_categories c
       LEFT JOIN plaza_posts p
         ON p.category_id = c.id AND p.status = 'published'
       WHERE c.is_active = 1
       GROUP BY c.id, c.name, c.slug, c.icon, c.sort_order
       ORDER BY c.sort_order ASC, c.name ASC`,
    );
    return rows.map((row) => ({
      id: row.id,
      name: row.name,
      slug: row.slug,
      icon: row.icon ?? '',
      post_count: Number(row.post_count ?? 0),
    }));
  };

  const resolveCategoryId = async (categoryId, categorySlug) => {
    await ensureCategories();
    if (categoryId) {
      const [rows] = await pool.query(
        `SELECT id FROM plaza_categories WHERE id = ? AND is_active = 1 LIMIT 1`,
        [categoryId],
      );
      if (!rows[0]) throw plazaError('分类不存在', 'category_not_found');
      return rows[0].id;
    }
    if (categorySlug) {
      const [rows] = await pool.query(
        `SELECT id FROM plaza_categories WHERE slug = ? AND is_active = 1 LIMIT 1`,
        [categorySlug],
      );
      if (!rows[0]) throw plazaError('分类不存在', 'category_not_found');
      return rows[0].id;
    }
    throw plazaError('必须选择分类', 'category_required');
  };

  const loadPublicationContext = async (userId, publicationId) => {
    const [rows] = await pool.query(
      `SELECT pr.id, pr.user_id, pr.status, pr.public_url,
              p.title, p.summary, p.cover_image_asset_id
       FROM h5_publish_records pr
       JOIN h5_page_records p ON p.id = pr.page_id
       WHERE pr.id = ? AND pr.user_id = ?
       LIMIT 1`,
      [publicationId, userId],
    );
    const row = rows[0];
    if (!row) throw plazaError('发布记录不存在', 'publication_not_found');
    if (row.status !== 'online') {
      throw plazaError('发布源不是在线状态', 'PUBLICATION_NOT_ONLINE');
    }
    return row;
  };

  const loadUserSnapshot = async (userId) => {
    const [rows] = await pool.query(
      `SELECT id, slug, display_name, username, plaza_post_banned
       FROM h5_users WHERE id = ? LIMIT 1`,
      [userId],
    );
    const row = rows[0];
    if (!row) throw plazaError('用户不存在', 'user_not_found');
    if (row.plaza_post_banned) throw plazaError('无发帖权限', 'POST_PERMISSION_DENIED');
    return {
      user_slug: row.slug || row.username,
      user_display_name: row.display_name || row.username,
      user_avatar_url: '',
    };
  };

  const createPost = async (userId, input) => {
    const publicationId = String(input?.publication_id ?? '').trim();
    if (!publicationId) throw plazaError('publication_id 不能为空', 'invalid_input');

    const [existing] = await pool.query(
      `SELECT id FROM plaza_posts WHERE publication_id = ? LIMIT 1`,
      [publicationId],
    );
    if (existing[0]) throw plazaError('该内容已发布到广场', 'ALREADY_PUBLISHED');

    const publication = await loadPublicationContext(userId, publicationId);
    const categoryId = await resolveCategoryId(input?.category_id, input?.category_slug);
    const userSnapshot = await loadUserSnapshot(userId);
    const tags = normalizeTags(input?.tags);
    const coverUrl = resolvePlazaCoverUrl(input?.cover_url, publication.public_url);
    const allowComment = input?.allow_comment == null ? true : Boolean(input.allow_comment);
    const now = Date.now();
    const postId = idFactory();
    const status = autoApprove ? 'published' : 'pending_review';
    const hotScore = status === 'published' ? await resolveHotScore(now) : 0;

    const conn = await pool.getConnection();
    try {
      await conn.beginTransaction();
      await conn.query(
        `INSERT INTO plaza_posts
         (id, publication_id, user_id, title, summary, cover_url,
          user_slug, user_display_name, user_avatar_url,
          category_id, tags, status, allow_comment, hot_score, hot_updated_at,
          published_at, created_at, updated_at)
         VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
        [
          postId,
          publicationId,
          userId,
          publication.title,
          publication.summary ?? '',
          coverUrl,
          userSnapshot.user_slug,
          userSnapshot.user_display_name,
          userSnapshot.user_avatar_url,
          categoryId,
          JSON.stringify(tags),
          status,
          allowComment ? 1 : 0,
          hotScore,
          status === 'published' ? now : null,
          now,
          now,
          now,
        ],
      );
      await conn.query(
        `UPDATE h5_users SET plaza_post_count = plaza_post_count + 1, updated_at = ? WHERE id = ?`,
        [now, userId],
      );
      await conn.commit();
    } catch (error) {
      await conn.rollback();
      throw error;
    } finally {
      conn.release();
    }

    if (status === 'published') {
      await plazaRedis?.invalidateFeedCaches?.();
      await onPostPublished?.(postId);
    }

    return { id: postId, status };
  };

  const updatePost = async (userId, postId, input) => {
    const [rows] = await pool.query(
      `SELECT pp.*, pr.public_url
       FROM plaza_posts pp
       JOIN h5_publish_records pr ON pr.id = pp.publication_id
       WHERE pp.id = ? AND pp.user_id = ? LIMIT 1`,
      [postId, userId],
    );
    const post = rows[0];
    if (!post) throw plazaError('帖子不存在', 'POST_NOT_FOUND');

    const updates = [];
    const params = [];
    if (input?.category_id != null || input?.category_slug != null) {
      const categoryId = await resolveCategoryId(input?.category_id, input?.category_slug);
      updates.push('category_id = ?');
      params.push(categoryId);
    }
    if (input?.tags != null) {
      updates.push('tags = ?');
      params.push(JSON.stringify(normalizeTags(input.tags)));
    }
    if (input?.cover_url != null) {
      updates.push('cover_url = ?');
      params.push(resolvePlazaCoverUrl(input.cover_url, post.public_url));
    }
    if (input?.allow_comment != null) {
      updates.push('allow_comment = ?');
      params.push(input.allow_comment ? 1 : 0);
    }
    if (updates.length === 0) throw plazaError('没有可更新的字段', 'invalid_input');

    let status = post.status;
    if (status === 'rejected') status = 'pending_review';
    updates.push('status = ?');
    updates.push('updated_at = ?');
    params.push(status, Date.now(), postId, userId);

    await pool.query(
      `UPDATE plaza_posts SET ${updates.join(', ')} WHERE id = ? AND user_id = ?`,
      params,
    );
    return { id: postId, status };
  };

  const hidePost = async (userId, postId) => {
    const now = Date.now();
    const [result] = await pool.query(
      `UPDATE plaza_posts SET status = 'hidden', updated_at = ?
       WHERE id = ? AND user_id = ? AND status != 'hidden'`,
      [now, postId, userId],
    );
    if (result.affectedRows === 0) throw plazaError('帖子不存在', 'POST_NOT_FOUND');
    return { id: postId, status: 'hidden' };
  };

  const hidePostsByPublicationId = async (conn, publicationId, now = Date.now()) => {
    await conn.query(
      `UPDATE plaza_posts SET status = 'hidden', updated_at = ?
       WHERE publication_id = ? AND status != 'hidden'`,
      [now, publicationId],
    );
  };

  const getPostById = async (postId, { viewerId = null, includeHidden = false } = {}) => {
    const statusClause = includeHidden ? '' : `AND pp.status = 'published'`;
    const [rows] = await pool.query(
      `SELECT pp.*, c.name AS category_name, c.slug AS category_slug, c.icon AS category_icon, pr.public_url
       FROM plaza_posts pp
       JOIN plaza_categories c ON c.id = pp.category_id
       JOIN h5_publish_records pr ON pr.id = pp.publication_id
       WHERE pp.id = ? ${statusClause}
       LIMIT 1`,
      [postId],
    );
    const row = rows[0];
    if (!row) throw plazaError('帖子不存在或已隐藏', 'POST_NOT_FOUND');
    let viewerReacted = null;
    if (viewerId && loadViewerReactions) {
      const map = await loadViewerReactions(viewerId, [postId]);
      viewerReacted = map.get(postId) ?? { liked: false, collected: false };
    }
    return formatPostRow(row, { viewerReacted, publicationUrl: row.public_url });
  };

  const listFeed = async ({
    sort = 'recommend',
    categorySlug = null,
    cursor = null,
    limit = 20,
    viewerId = null,
    sessionId = null,
  } = {}) => {
    await ensureCategories();
    const normalizedSort = normalizeSort(sort);
    const pageLimit = clampLimit(limit);

    if (normalizedSort === 'recommend' && recommendService) {
      return recommendService.listRecommendedFeed({
        viewerId,
        sessionId,
        categorySlug,
        cursor,
        limit: pageLimit,
      });
    }

    if (!cursor && normalizedSort !== 'recommend' && plazaRedis?.getFeedCache) {
      const cached = await plazaRedis.getFeedCache(normalizedSort, categorySlug, null);
      if (cached?.posts) {
        if (viewerId && loadViewerReactions) {
          const reactionMap = await loadViewerReactions(
            viewerId,
            cached.posts.map((post) => post.id),
          );
          cached.posts = cached.posts.map((post) => ({
            ...post,
            viewer_reacted: reactionMap.get(post.id) ?? null,
          }));
        }
        return cached;
      }
    }

    const params = ['published'];
    let categoryFilter = '';
    if (categorySlug) {
      categoryFilter = 'AND c.slug = ?';
      params.push(categorySlug);
    }

    let cursorClause = '';
    if (cursor) {
      const [cursorRows] = await pool.query(
        `SELECT id, hot_score, published_at FROM plaza_posts WHERE id = ? LIMIT 1`,
        [cursor],
      );
      const cursorRow = cursorRows[0];
      if (cursorRow) {
        if (normalizedSort === 'new') {
          cursorClause =
            'AND (pp.published_at < ? OR (pp.published_at = ? AND pp.id < ?))';
          params.push(cursorRow.published_at, cursorRow.published_at, cursor);
        } else {
          cursorClause =
            'AND (pp.hot_score < ? OR (pp.hot_score = ? AND pp.id < ?))';
          params.push(cursorRow.hot_score, cursorRow.hot_score, cursor);
        }
      }
    }

    const orderBy =
      normalizedSort === 'new'
        ? 'pp.published_at DESC, pp.id DESC'
        : 'pp.hot_score DESC, pp.id DESC';

    const [rows] = await pool.query(
      `SELECT pp.*, c.name AS category_name, c.slug AS category_slug, c.icon AS category_icon, pr.public_url
       FROM plaza_posts pp
       JOIN plaza_categories c ON c.id = pp.category_id
       JOIN h5_publish_records pr ON pr.id = pp.publication_id
       WHERE pp.status = ?
       ${categoryFilter}
       ${cursorClause}
       ORDER BY ${orderBy}
       LIMIT ?`,
      [...params, pageLimit + 1],
    );

    const hasMore = rows.length > pageLimit;
    const pageRows = hasMore ? rows.slice(0, pageLimit) : rows;
    let reactionMap = new Map();
    if (viewerId && loadViewerReactions) {
      reactionMap = await loadViewerReactions(
        viewerId,
        pageRows.map((row) => row.id),
      );
    }
    const posts = pageRows.map((row) =>
      formatPostRow(row, {
        viewerReacted: viewerId ? reactionMap.get(row.id) ?? null : null,
      }),
    );

    let featured = { homepage_banner: [], trending: [], category_top: {} };
    if (!cursor && loadFeaturedPosts) {
      featured = await loadFeaturedPosts(viewerId);
    }

    let mergedPosts = posts;
    if (!cursor && normalizedSort === 'hot' && featured.trending.length > 0) {
      const trending = featured.trending.slice(0, 10);
      const trendingIds = new Set(trending.map((post) => post.id));
      mergedPosts = [...trending, ...posts.filter((post) => !trendingIds.has(post.id))].slice(
        0,
        pageLimit,
      );
    }
    if (
      !cursor &&
      normalizedSort === 'hot' &&
      categorySlug &&
      featured.category_top?.[categorySlug]?.length
    ) {
      const tops = featured.category_top[categorySlug].slice(0, 3);
      const topIds = new Set(tops.map((post) => post.id));
      mergedPosts = [...tops, ...mergedPosts.filter((post) => !topIds.has(post.id))].slice(
        0,
        pageLimit,
      );
    }
    if (!cursor && normalizedSort === 'hot') {
      mergedPosts = promoteFreshPosts(mergedPosts).slice(0, pageLimit);
    }

    const result = {
      posts: mergedPosts,
      featured: { homepage_banner: featured.homepage_banner, trending: [] },
      next_cursor: hasMore ? pageRows[pageRows.length - 1].id : null,
      has_more: hasMore,
    };

    if (!cursor && plazaRedis?.setFeedCache) {
      const ttl = normalizedSort === 'new' ? 60 : 300;
      await plazaRedis.setFeedCache(
        normalizedSort,
        categorySlug,
        null,
        {
          posts: mergedPosts,
          featured: result.featured,
          next_cursor: result.next_cursor,
          has_more: result.has_more,
        },
        ttl,
      );
    }

    return result;
  };

  const reviewPost = async (postId, action, { reason = null } = {}) => {
    const now = Date.now();
    if (action === 'approve') {
      const hotScore = await resolveHotScore(now);
      await pool.query(
        `UPDATE plaza_posts
         SET status = 'published', hot_score = ?, hot_updated_at = ?, updated_at = ?
         WHERE id = ?`,
        [hotScore, now, now, postId],
      );
      await plazaRedis?.invalidateFeedCaches?.();
      await onPostPublished?.(postId);
      return { id: postId, status: 'published' };
    }
    if (action === 'reject') {
      if (!reason) throw plazaError('拒绝时必须填写原因', 'invalid_input');
      await pool.query(
        `UPDATE plaza_posts SET status = 'rejected', updated_at = ? WHERE id = ?`,
        [now, postId],
      );
      return { id: postId, status: 'rejected', reason };
    }
    if (action === 'hide') {
      await pool.query(
        `UPDATE plaza_posts SET status = 'hidden', updated_at = ? WHERE id = ?`,
        [now, postId],
      );
      return { id: postId, status: 'hidden' };
    }
    throw plazaError('不支持的审核操作', 'invalid_input');
  };

  const listPendingPosts = async (limit = 50) => {
    const [rows] = await pool.query(
      `SELECT id, title, status, user_display_name, user_slug, published_at
       FROM plaza_posts
       WHERE status = 'pending_review'
       ORDER BY published_at DESC
       LIMIT ?`,
      [clampLimit(limit, 50, 100)],
    );
    return rows.map((row) => ({
      id: row.id,
      title: row.title,
      status: row.status,
      author: row.user_display_name,
      author_slug: row.user_slug,
      published_at: new Date(Number(row.published_at)).toISOString(),
    }));
  };

  return {
    ensureCategories,
    listCategories,
    createPost,
    updatePost,
    hidePost,
    hidePostsByPublicationId,
    getPostById,
    listFeed,
    reviewPost,
    listPendingPosts,
  };
}

export function mapPlazaError(error) {
  const code = error?.code;
  const map = {
    PUBLICATION_NOT_ONLINE: 422,
    ALREADY_PUBLISHED: 409,
    POST_NOT_FOUND: 404,
    POST_PERMISSION_DENIED: 403,
    publication_not_found: 403,
    category_not_found: 422,
    category_required: 422,
    invalid_input: 422,
    COMMENT_DISABLED: 422,
    COMMENT_TOO_LONG: 422,
    COMMENT_RATE_LIMITED: 429,
    REPLY_DEPTH_EXCEEDED: 422,
    SELF_FOLLOW: 422,
    COMMENT_NOT_FOUND: 404,
    OPS_PERMISSION_DENIED: 403,
    report_not_found: 404,
    featured_not_found: 404,
    user_not_found: 404,
  };
  return map[code] ?? 500;
}

export { formatPostRow };
