import crypto from 'node:crypto';
import { loadMindSpaceConfig } from './mindspace-config.mjs';

export const DEFAULT_SPACE_QUOTA_BYTES = 5 * 1024 * 1024;
export const DEFAULT_MAX_FILE_BYTES = 5 * 1024 * 1024;

export const SYSTEM_CATEGORIES = Object.freeze([
  {
    code: 'oa',
    name: 'OA 工作区',
    visibilityPolicy: 'private',
    aiAccessPolicy: 'selected_assets',
    publishPolicy: 'derivative_only',
    sortOrder: 10,
  },
  {
    code: 'private',
    name: '私人区',
    visibilityPolicy: 'private',
    aiAccessPolicy: 'explicit_asset_grant',
    publishPolicy: 'desensitized_copy_only',
    sortOrder: 20,
  },
  {
    code: 'public',
    name: '公开区',
    visibilityPolicy: 'public_candidate',
    aiAccessPolicy: 'selected_assets',
    publishPolicy: 'security_scan_required',
    sortOrder: 30,
  },
  {
    code: 'draft',
    name: '页面草稿',
    visibilityPolicy: 'private',
    aiAccessPolicy: 'job_output',
    publishPolicy: 'security_scan_required',
    sortOrder: 40,
  },
  {
    code: 'archive',
    name: '归档区',
    visibilityPolicy: 'private',
    aiAccessPolicy: 'none',
    publishPolicy: 'forbidden',
    sortOrder: 50,
  },
]);

function asNumber(value) {
  return Number(value ?? 0);
}

function categoryResponse(row) {
  return {
    id: row.id,
    code: row.category_code,
    name: row.category_name,
    visibilityPolicy: row.visibility_policy,
    aiAccessPolicy: row.ai_access_policy,
    publishPolicy: row.publish_policy,
    isSystem: Boolean(row.is_system),
    sortOrder: asNumber(row.sort_order),
    itemCount: asNumber(row.item_count),
  };
}

export async function initializeDefaultSpace(
  db,
  userId,
  {
    quotaBytes = DEFAULT_SPACE_QUOTA_BYTES,
    spaceName = '我的空间',
    now = Date.now(),
    idFactory = () => crypto.randomUUID(),
  } = {},
) {
  const spaceId = idFactory();
  await db.query(
    `INSERT INTO h5_user_spaces
     (id, user_id, space_name, quota_bytes, used_bytes, reserved_bytes, status, created_at, updated_at)
     VALUES (?, ?, ?, ?, 0, 0, 'active', ?, ?)
     ON DUPLICATE KEY UPDATE user_id = user_id`,
    [spaceId, userId, spaceName, quotaBytes, now, now],
  );

  const [spaces] = await db.query(
    `SELECT id FROM h5_user_spaces WHERE user_id = ? LIMIT 1`,
    [userId],
  );
  const resolvedSpaceId = spaces[0]?.id;
  if (!resolvedSpaceId) {
    throw new Error('用户空间初始化失败');
  }

  for (const category of SYSTEM_CATEGORIES) {
    await db.query(
      `INSERT INTO h5_space_categories
       (id, user_id, space_id, category_code, category_name, visibility_policy,
        ai_access_policy, publish_policy, is_system, sort_order, created_at, updated_at)
       VALUES (?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?)
       ON DUPLICATE KEY UPDATE
         category_name = VALUES(category_name),
         visibility_policy = VALUES(visibility_policy),
         ai_access_policy = VALUES(ai_access_policy),
         publish_policy = VALUES(publish_policy),
         is_system = 1,
         sort_order = VALUES(sort_order),
         updated_at = VALUES(updated_at)`,
      [
        idFactory(),
        userId,
        resolvedSpaceId,
        category.code,
        category.name,
        category.visibilityPolicy,
        category.aiAccessPolicy,
        category.publishPolicy,
        category.sortOrder,
        now,
        now,
      ],
    );
  }

  return resolvedSpaceId;
}

export async function ensureDefaultSpaces(pool, options = {}) {
  const [users] = await pool.query(
    `SELECT u.id
     FROM h5_users u
     LEFT JOIN h5_user_spaces s ON s.user_id = u.id
     WHERE u.role = 'user' AND s.id IS NULL`,
  );
  for (const user of users) {
    await initializeDefaultSpace(pool, user.id, options);
  }
  return users.length;
}

export function createMindSpaceService(pool, options = {}) {
  const maxFileBytes = Number(options.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES);
  const aiDailyLimit = Number(options.aiDailyLimit ?? 10);
  const publicPageLimitFallback = Number(options.publicPageLimit ?? 5);
  const monthlyViewLimit = Number(options.monthlyViewLimit ?? 1000);
  const scheduleService = options.scheduleService ?? null;
  const resolvePublicPageLimit = async () => {
    try {
      const config = await loadMindSpaceConfig(pool);
      return Number(config.publicPageLimit ?? publicPageLimitFallback);
    } catch {
      return publicPageLimitFallback;
    }
  };

  const getSpace = async (userId) => {
    const [spaces] = await pool.query(
      `SELECT id, user_id, space_name, quota_bytes, used_bytes, reserved_bytes, status,
              created_at, updated_at
       FROM h5_user_spaces
       WHERE user_id = ?
       LIMIT 1`,
      [userId],
    );
    const row = spaces[0];
    if (!row) return null;

    const [categories] = await pool.query(
      `SELECT c.id, c.category_code, c.category_name, c.visibility_policy,
              c.ai_access_policy, c.publish_policy, c.is_system, c.sort_order,
              CASE
                WHEN c.category_code = 'draft' THEN COUNT(DISTINCT p.id)
                WHEN c.category_code = 'public' THEN COUNT(DISTINCT pr.page_id)
                ELSE COUNT(DISTINCT a.id)
              END AS item_count
       FROM h5_space_categories c
       LEFT JOIN h5_assets a
         ON a.category_id = c.id
        AND a.user_id = c.user_id
        AND a.status <> 'deleted'
        AND a.source_type = 'upload'
       LEFT JOIN h5_page_records p
         ON p.category_id = c.id AND p.user_id = c.user_id AND p.status <> 'deleted'
       LEFT JOIN h5_publish_records pr
         ON pr.user_id = c.user_id AND pr.status = 'online'
       WHERE c.user_id = ? AND c.space_id = ?
       GROUP BY c.id, c.category_code, c.category_name, c.visibility_policy,
                c.ai_access_policy, c.publish_policy, c.is_system, c.sort_order
       ORDER BY c.sort_order, c.category_name`,
      [userId, row.id],
    );

    const quotaBytes = asNumber(row.quota_bytes);
    const usedBytes = asNumber(row.used_bytes);
    const reservedBytes = asNumber(row.reserved_bytes);
    const [publicationUsage] = await pool.query(
      `SELECT COUNT(DISTINCT CASE WHEN status = 'online' THEN page_id END) AS public_page_used,
              COALESCE(SUM(CASE WHEN published_at >= ? THEN view_count ELSE 0 END), 0) AS monthly_view_used
       FROM h5_publish_records WHERE user_id = ?`,
      [Date.now() - 30 * 24 * 60 * 60 * 1000, userId],
    );
    const publicPageLimit = await resolvePublicPageLimit();
    let schedule = null;
    if (scheduleService) {
      try {
        const [todayTodoItems, digestSubscriptions] = await Promise.all([
          typeof scheduleService.listTodayTodoItems === 'function'
            ? scheduleService.listTodayTodoItems({ userId })
            : Promise.resolve([]),
          typeof scheduleService.listDigestSubscriptions === 'function'
            ? scheduleService.listDigestSubscriptions({
                userId,
                digestType: 'todo_day',
                status: ['active', 'locked'],
                limit: 10,
              })
            : Promise.resolve([]),
        ]);
        schedule = { todayTodoItems, digestSubscriptions };
      } catch {
        schedule = null;
      }
    }
    return {
      id: row.id,
      userId: row.user_id,
      name: row.space_name,
      status: row.status,
      quota: {
        quotaBytes,
        usedBytes,
        reservedBytes,
        availableBytes: Math.max(0, quotaBytes - usedBytes - reservedBytes),
        maxFileBytes,
        publicPageLimit,
        publicPageUsed: asNumber(publicationUsage[0]?.public_page_used),
        aiDailyLimit,
        aiDailyUsed: 0,
        monthlyViewLimit,
        monthlyViewUsed: asNumber(publicationUsage[0]?.monthly_view_used),
      },
      categories: categories.map(categoryResponse),
      createdAt: asNumber(row.created_at),
      updatedAt: asNumber(row.updated_at),
      schedule,
    };
  };

  const getQuota = async (userId) => {
    const space = await getSpace(userId);
    return space?.quota ?? null;
  };

  const listCategories = async (userId) => {
    const space = await getSpace(userId);
    return space?.categories ?? null;
  };

  return {
    getSpace,
    getQuota,
    listCategories,
  };
}
