-- =====================================================
-- 积分过期提醒功能 - 数据库脚本
-- 创建日期: 2026-03-06
-- =====================================================

-- =====================================================
-- 一、更新现有表结构
-- =====================================================

-- 为积分记录表添加过期时间字段（如果不存在）
ALTER TABLE ml_point_records ADD COLUMN IF NOT EXISTS expires_at TIMESTAMPTZ;
ALTER TABLE ml_point_records ADD COLUMN IF NOT EXISTS is_expired BOOLEAN DEFAULT FALSE;
ALTER TABLE ml_point_records ADD COLUMN IF NOT EXISTS expired_at TIMESTAMPTZ;

-- 为积分记录表添加余额字段（记录变动后余额）
ALTER TABLE ml_point_records ADD COLUMN IF NOT EXISTS balance_after INT;

-- 添加索引
CREATE INDEX IF NOT EXISTS idx_point_records_expires_at ON ml_point_records(expires_at);
CREATE INDEX IF NOT EXISTS idx_point_records_is_expired ON ml_point_records(is_expired);

-- 更新用户积分表，添加即将过期积分字段
ALTER TABLE ml_user_points ADD COLUMN IF NOT EXISTS expiring_points INT DEFAULT 0;
ALTER TABLE ml_user_points ADD COLUMN IF NOT EXISTS expiring_date DATE;

COMMENT ON COLUMN ml_point_records.expires_at IS '积分过期时间，NULL表示永不过期';
COMMENT ON COLUMN ml_point_records.is_expired IS '是否已过期';
COMMENT ON COLUMN ml_point_records.expired_at IS '实际过期时间';
COMMENT ON COLUMN ml_user_points.expiring_points IS '即将过期积分（30天内）';
COMMENT ON COLUMN ml_user_points.expiring_date IS '最近过期日期';

-- =====================================================
-- 二、创建积分过期通知表
-- =====================================================

CREATE TABLE IF NOT EXISTS ml_point_expiry_notifications (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  user_id UUID NOT NULL REFERENCES auth.users(id),
  points_expiring INT NOT NULL,
  expiry_date DATE NOT NULL,
  notification_type VARCHAR(20) NOT NULL, -- '7days', '3days', '1day'
  is_sent BOOLEAN DEFAULT FALSE,
  sent_at TIMESTAMPTZ,
  created_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE INDEX IF NOT EXISTS idx_expiry_notifications_user_id ON ml_point_expiry_notifications(user_id);
CREATE INDEX IF NOT EXISTS idx_expiry_notifications_is_sent ON ml_point_expiry_notifications(is_sent);

COMMENT ON TABLE ml_point_expiry_notifications IS '积分过期通知记录表';

-- =====================================================
-- 三、创建积分过期配置表
-- =====================================================

CREATE TABLE IF NOT EXISTS ml_point_expiry_config (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  config_key VARCHAR(50) NOT NULL UNIQUE,
  config_value JSONB NOT NULL,
  description TEXT,
  updated_at TIMESTAMPTZ DEFAULT NOW()
);

-- 初始化配置
INSERT INTO ml_point_expiry_config (config_key, config_value, description) VALUES
('expiry_period', '{"days": 365}', '积分有效期（天），NULL或0表示永不过期'),
('notification_days', '{"days": [7, 3, 1]}', '过期前多少天发送提醒'),
('enabled', '{"value": true}', '是否启用积分过期功能')
ON CONFLICT (config_key) DO NOTHING;

COMMENT ON TABLE ml_point_expiry_config IS '积分过期配置表';

-- =====================================================
-- 四、创建定时任务函数
-- =====================================================

-- 1. 为新获取的积分设置过期时间
CREATE OR REPLACE FUNCTION set_points_expiry()
RETURNS void AS $$
DECLARE
  expiry_days INT;
  enabled BOOLEAN;
BEGIN
  -- 检查是否启用过期功能
  SELECT (config_value->>'value')::boolean INTO enabled
  FROM ml_point_expiry_config
  WHERE config_key = 'enabled';
  
  IF NOT enabled THEN
    RETURN;
  END IF;
  
  -- 获取过期天数
  SELECT (config_value->>'days')::int INTO expiry_days
  FROM ml_point_expiry_config
  WHERE config_key = 'expiry_period';
  
  -- 为未设置过期时间且未过期的积分记录设置过期时间
  -- 只处理正积分（获取的积分）
  UPDATE ml_point_records
  SET expires_at = created_at + (expiry_days || ' days')::interval
  WHERE expires_at IS NULL
    AND is_expired = FALSE
    AND points > 0;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;

-- 2. 处理过期积分
CREATE OR REPLACE FUNCTION process_expired_points()
RETURNS void AS $$
DECLARE
  expired_record RECORD;
  user_points_rec RECORD;
BEGIN
  -- 查找所有已过期但未标记的积分记录
  FOR expired_record IN 
    SELECT id, user_id, points, expires_at
    FROM ml_point_records
    WHERE expires_at IS NOT NULL
      AND expires_at < NOW()
      AND is_expired = FALSE
      AND points > 0
  LOOP
    -- 标记为已过期
    UPDATE ml_point_records
    SET is_expired = TRUE, expired_at = NOW()
    WHERE id = expired_record.id;
    
    -- 扣减用户积分
    UPDATE ml_user_points
    SET points = GREATEST(0, points - expired_record.points),
        updated_at = NOW()
    WHERE user_id = expired_record.user_id;
    
    -- 记录过期扣减
    INSERT INTO ml_point_records (user_id, points, type, description, created_at)
    VALUES (
      expired_record.user_id,
      -expired_record.points,
      'expire',
      '积分过期自动扣除',
      NOW()
    );
  END LOOP;
  
  -- 更新用户即将过期积分统计
  FOR user_points_rec IN SELECT user_id FROM ml_user_points LOOP
    UPDATE ml_user_points up
    SET expiring_points = (
        SELECT COALESCE(SUM(points), 0)
        FROM ml_point_records
        WHERE user_id = up.user_id
          AND points > 0
          AND is_expired = FALSE
          AND expires_at IS NOT NULL
          AND expires_at < NOW() + INTERVAL '30 days'
      ),
      expiring_date = (
        SELECT MIN(expires_at)::date
        FROM ml_point_records
        WHERE user_id = up.user_id
          AND points > 0
          AND is_expired = FALSE
          AND expires_at IS NOT NULL
          AND expires_at < NOW() + INTERVAL '30 days'
      )
    WHERE up.user_id = user_points_rec.user_id;
  END LOOP;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;

-- 3. 生成过期提醒通知
CREATE OR REPLACE FUNCTION generate_expiry_notifications()
RETURNS void AS $$
DECLARE
  notification_days INT[];
  days_before INT;
  expiring_record RECORD;
BEGIN
  -- 获取提醒天数配置
  SELECT ARRAY(SELECT jsonb_array_elements_text(config_value->'days')::int)
  INTO notification_days
  FROM ml_point_expiry_config
  WHERE config_key = 'notification_days';
  
  -- 遍历每个提醒天数
  FOREACH days_before IN ARRAY notification_days LOOP
    -- 查找即将过期的积分
    FOR expiring_record IN
      SELECT 
        user_id,
        SUM(points) as total_points,
        MIN(expires_at)::date as expiry_date
      FROM ml_point_records
      WHERE points > 0
        AND is_expired = FALSE
        AND expires_at IS NOT NULL
        AND expires_at::date = (CURRENT_DATE + days_before)
      GROUP BY user_id
    LOOP
      -- 检查是否已发送过该类型通知
      IF NOT EXISTS (
        SELECT 1 FROM ml_point_expiry_notifications
        WHERE user_id = expiring_record.user_id
          AND expiry_date = expiring_record.expiry_date
          AND notification_type = days_before || 'days'
      ) THEN
        -- 插入通知记录
        INSERT INTO ml_point_expiry_notifications 
          (user_id, points_expiring, expiry_date, notification_type)
        VALUES (
          expiring_record.user_id,
          expiring_record.total_points,
          expiring_record.expiry_date,
          days_before || 'days'
        );
      END IF;
    END LOOP;
  END LOOP;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;

-- 4. 获取用户即将过期积分（供前端调用）
CREATE OR REPLACE FUNCTION get_user_expiring_points(p_user_id UUID)
RETURNS TABLE(
  expiring_points INT,
  expiring_date DATE,
  expiring_details JSONB
) AS $$
BEGIN
  RETURN QUERY
  SELECT 
    COALESCE(SUM(pr.points), 0)::INT as expiring_points,
    MIN(pr.expires_at)::date as expiring_date,
    jsonb_agg(
      jsonb_build_object(
        'points', pr.points,
        'expires_at', pr.expires_at,
        'description', pr.description,
        'created_at', pr.created_at
      )
    ) as expiring_details
  FROM ml_point_records pr
  WHERE pr.user_id = p_user_id
    AND pr.points > 0
    AND pr.is_expired = FALSE
    AND pr.expires_at IS NOT NULL
    AND pr.expires_at < NOW() + INTERVAL '30 days';
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;

-- 5. 综合定时任务（每天执行一次）
CREATE OR REPLACE FUNCTION daily_points_maintenance()
RETURNS void AS $$
BEGIN
  -- 设置新积分的过期时间
  PERFORM set_points_expiry();
  
  -- 处理已过期积分
  PERFORM process_expired_points();
  
  -- 生成过期提醒
  PERFORM generate_expiry_notifications();
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;

-- =====================================================
-- 五、配置 pg_cron 定时任务（如果可用）
-- =====================================================

-- 注意：需要先在 Supabase 中启用 pg_cron 扩展
-- 可以在 Supabase Dashboard -> Database -> Extensions 中启用

-- 每天凌晨2点执行积分维护任务
-- SELECT cron.schedule(
--   'daily-points-maintenance',
--   '0 2 * * *',
--   'SELECT daily_points_maintenance()'
-- );

-- 每小时检查并发送过期提醒（可选）
-- SELECT cron.schedule(
--   'hourly-expiry-notification-check',
--   '0 * * * *',
--   'SELECT generate_expiry_notifications()'
-- );

-- =====================================================
-- 六、RLS 策略
-- =====================================================

ALTER TABLE ml_point_expiry_notifications ENABLE ROW LEVEL SECURITY;

CREATE POLICY "Users can view own expiry notifications" 
ON ml_point_expiry_notifications FOR SELECT 
TO authenticated 
USING (auth.uid() = user_id);

-- =====================================================
-- 七、创建视图方便查询
-- =====================================================

-- 用户积分概览视图
CREATE OR REPLACE VIEW ml_user_points_overview AS
SELECT 
  up.user_id,
  up.points as current_points,
  up.total_earned,
  COALESCE(up.expiring_points, 0) as expiring_points,
  up.expiring_date,
  (SELECT COUNT(*) FROM ml_signin_records WHERE user_id = up.user_id) as total_signin_days,
  (SELECT MAX(continuous_days) FROM ml_signin_records WHERE user_id = up.user_id) as max_continuous_days
FROM ml_user_points up;

-- =====================================================
-- 八、测试数据（可选）
-- =====================================================

-- 插入一些即将过期的测试积分记录
-- INSERT INTO ml_point_records (user_id, points, type, description, expires_at, created_at)
-- VALUES 
--   ('YOUR_USER_ID', 50, 'signin', '测试即将过期积分', NOW() + INTERVAL '5 days', NOW() - INTERVAL '360 days'),
--   ('YOUR_USER_ID', 30, 'shopping', '测试购物积分', NOW() + INTERVAL '10 days', NOW() - INTERVAL '355 days');

-- =====================================================
-- 九、使用说明
-- =====================================================

/*
使用方法：

1. 手动执行定时任务：
   SELECT daily_points_maintenance();

2. 查询用户即将过期积分：
   SELECT * FROM get_user_expiring_points('用户ID');

3. 修改积分有效期配置：
   UPDATE ml_point_expiry_config 
   SET config_value = '{"days": 180}', updated_at = NOW()
   WHERE config_key = 'expiry_period';

4. 禁用积分过期功能：
   UPDATE ml_point_expiry_config 
   SET config_value = '{"value": false}', updated_at = NOW()
   WHERE config_key = 'enabled';

5. 修改提醒天数：
   UPDATE ml_point_expiry_config 
   SET config_value = '{"days": [14, 7, 3, 1]}', updated_at = NOW()
   WHERE config_key = 'notification_days';

注意：
- 如果 Supabase 项目启用了 pg_cron，可以取消注释定时任务配置
- 如果没有 pg_cron，需要通过外部定时任务（如 Supabase Edge Functions）调用 daily_points_maintenance()
*/
