-- =====================================================================================
-- Add push device table and express_notifications table
-- 目的：保存每个用户/商家设备的推送 CID，并记录由轨迹事件触发的通知消息
-- 创建日期：2026-02-24
-- 注意：尽量与仓库中其它迁移风格保持一致；该脚本对已存在对象做 IF NOT EXISTS 检查。
-- =====================================================================================

BEGIN;

-- 依赖扩展（若已存在则忽略）
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";

-- updated_at 自动维护函数（若仓库已有同名函数则不重复创建）
DO $do$
BEGIN
  IF to_regprocedure('public.update_updated_at_column()') IS NULL THEN
    CREATE OR REPLACE FUNCTION public.update_updated_at_column()
    RETURNS TRIGGER
    LANGUAGE plpgsql
    AS $func$
    BEGIN
      NEW.updated_at = NOW();
      RETURN NEW;
    END;
    $func$;
  END IF;
END $do$;

-- =====================================================
-- A. push_devices：设备注册/绑定表
-- =====================================================
CREATE TABLE IF NOT EXISTS public.push_devices (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),

  -- 关联主体（user / merchant），使用具体字段便于查询与权限控制。
  user_id UUID NULL,
  merchant_id UUID NULL,

  -- 设备推送客户端标识（CID）由推送 SDK 提供
  cid VARCHAR(255) NOT NULL,

  -- 平台/渠道（android / ios / web / huawei / xiaomi）
  platform VARCHAR(32) NOT NULL DEFAULT 'android',

  -- 应用标识（用于区分不同打包的 AppID / 环境）
  appid VARCHAR(128) NOT NULL DEFAULT 'default',

  -- 设备是否激活（登录/解绑/失效时置 false）
  is_active BOOLEAN NOT NULL DEFAULT TRUE,

  -- 设备最后上报时间（用于清理失效设备）
  last_seen_at TIMESTAMP WITH TIME ZONE NULL,

  -- 注册来源（可选，用于审计）
  registration_source VARCHAR(64) NULL,

  created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
  updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
);

-- updated_at 触发器
DO $$
BEGIN
  IF NOT EXISTS (
    SELECT 1 FROM pg_trigger
    WHERE tgname = 'trigger_push_devices_updated_at'
  ) THEN
    CREATE TRIGGER trigger_push_devices_updated_at
    BEFORE UPDATE ON public.push_devices
    FOR EACH ROW EXECUTE FUNCTION public.update_updated_at_column();
  END IF;
END $$;

-- 唯一约束：同一 appid 下 cid 唯一
CREATE UNIQUE INDEX IF NOT EXISTS ux_push_devices_appid_cid
  ON public.push_devices(appid, cid);

-- 索引：按 user_id/merchant_id 查询是常用路径
CREATE INDEX IF NOT EXISTS idx_push_devices_user_id ON public.push_devices(user_id);
CREATE INDEX IF NOT EXISTS idx_push_devices_merchant_id ON public.push_devices(merchant_id);
CREATE INDEX IF NOT EXISTS idx_push_devices_is_active ON public.push_devices(is_active);

-- =====================================================
-- B. express_notifications：物流相关推送消息中心表
-- 用途：保存由轨迹事件触发的消息记录（用于消息中心、未读计数与幂等）
-- =====================================================
CREATE TABLE IF NOT EXISTS public.express_notifications (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),

  -- 接收范围：user / merchant
  aud VARCHAR(16) NOT NULL,
  recipient_id UUID NOT NULL,

  -- 业务关联（可选）：订单/运单
  order_id UUID NULL,
  waybill_id UUID NULL,

  -- 运单与承运方冗余字段，便于快速查询
  tracking_no VARCHAR(64) NULL,
  carrier VARCHAR(32) NULL,

  -- 推送消息唯一 id（可由服务端生成并返回给客户端）
  message_id VARCHAR(128) NULL,

  -- 事件摘要（下发给客户端的安全/脱敏文案）
  event_text_safe TEXT NULL,
  status_code VARCHAR(32) NULL,
  retry_count INTEGER NOT NULL DEFAULT 0,
  last_error TEXT NULL,
  next_attempt_at TIMESTAMP WITH TIME ZONE NULL,
  event_time TIMESTAMP WITH TIME ZONE NULL,

  -- 透传/审计用 payload（不包含敏感字段，raw_payload 请勿透传给客户端）
  payload JSONB NULL,

  -- 已读时间（null 表示未读）
  read_at TIMESTAMP WITH TIME ZONE NULL,

  -- 幂等键（确保同一事件只生成一条消息）
  dedupe_key VARCHAR(256) NULL,

  created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
  updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
);

-- updated_at 触发器
DO $$
BEGIN
  IF NOT EXISTS (
    SELECT 1 FROM pg_trigger
    WHERE tgname = 'trigger_express_notifications_updated_at'
  ) THEN
    CREATE TRIGGER trigger_express_notifications_updated_at
    BEFORE UPDATE ON public.express_notifications
    FOR EACH ROW EXECUTE FUNCTION public.update_updated_at_column();
  END IF;
END $$;

-- 索引与约束
CREATE INDEX IF NOT EXISTS idx_express_notifications_aud_recipient ON public.express_notifications(aud, recipient_id);
CREATE INDEX IF NOT EXISTS idx_express_notifications_order_id ON public.express_notifications(order_id);
CREATE INDEX IF NOT EXISTS idx_express_notifications_waybill_id ON public.express_notifications(waybill_id);
CREATE INDEX IF NOT EXISTS idx_express_notifications_created_at ON public.express_notifications(created_at);
CREATE INDEX IF NOT EXISTS idx_express_notifications_read_at ON public.express_notifications(read_at);

-- 若使用 message_id 做幂等（外部系统/队列），则建立唯一索引
-- 注意：这里不能使用“部分唯一索引（WHERE message_id IS NOT NULL）”，
-- 否则 PostgREST 的 upsert `?on_conflict=message_id` 会触发 42P10：
-- "there is no unique or exclusion constraint matching the ON CONFLICT specification"
-- 普通 UNIQUE INDEX 仍允许多个 NULL（符合历史兼容）。
CREATE UNIQUE INDEX IF NOT EXISTS ux_express_notifications_message_id
  ON public.express_notifications(message_id);

-- =====================================================
-- C. 兼容性与外键（若目标表存在则添加外键约束；若不存在则保留字段）
-- 说明：为避免迁移失败，我们在这里尝试添加外键，但使用 DO $$ ... EXCEPTION 来忽略不存在表的情况。
-- =====================================================
DO $$
BEGIN
  -- 尝试为 push_devices.user_id 添加外键（如果 ak_users 表存在）
  IF EXISTS (SELECT 1 FROM pg_class c JOIN pg_namespace n ON c.relnamespace = n.oid WHERE c.relname = 'ak_users' AND n.nspname = 'public') THEN
    BEGIN
      ALTER TABLE public.push_devices
      ADD CONSTRAINT fk_push_devices_user FOREIGN KEY (user_id) REFERENCES public.ak_users(id) ON DELETE SET NULL;
    EXCEPTION WHEN duplicate_object THEN
      -- 已存在约束则忽略
      NULL;
    END;
  END IF;

  -- 尝试为 express_notifications.order_id 添加外键（如果 ml_orders 表存在）
  IF EXISTS (SELECT 1 FROM pg_class c JOIN pg_namespace n ON c.relnamespace = n.oid WHERE c.relname = 'ml_orders' AND n.nspname = 'public') THEN
    BEGIN
      ALTER TABLE public.express_notifications
      ADD CONSTRAINT fk_express_notifications_order FOREIGN KEY (order_id) REFERENCES public.ml_orders(id) ON DELETE SET NULL;
    EXCEPTION WHEN duplicate_object THEN
      NULL;
    END;
  END IF;

  -- 尝试为 express_notifications.waybill_id 添加外键（如果 platform_express_waybills 表存在）
  IF EXISTS (SELECT 1 FROM pg_class c JOIN pg_namespace n ON c.relnamespace = n.oid WHERE c.relname = 'platform_express_waybills' AND n.nspname = 'public') THEN
    BEGIN
      ALTER TABLE public.express_notifications
      ADD CONSTRAINT fk_express_notifications_waybill FOREIGN KEY (waybill_id) REFERENCES public.platform_express_waybills(id) ON DELETE SET NULL;
    EXCEPTION WHEN duplicate_object THEN
      NULL;
    END;
  END IF;
END $$;

COMMIT;

-- =====================================================================================
-- 使用说明（简要）
-- 1) `push_devices` 存储设备 CID 与所属主体（user_id / merchant_id），提供活跃检测与解绑能力。
-- 2) `express_notifications` 存储消息中心记录；生成通知时写入该表並异步向 `push_devices` 中匹配设备发送推送。
-- 3) 推荐在应用层实现：当用户登出或解绑设备时将 `push_devices.is_active` 置 false。
-- 4) 可按需要添加清理任务：清理 long-inactive 的 `push_devices`（例如 180 天未上报）。
-- =====================================================================================
