-- 物流轨迹（第三方/Mock）数据库设计（PostgreSQL / Supabase 兼容）
-- 重要说明（生产环境请勿直接执行本文件）：
-- - 本文件包含平台侧表 + mock_* 测试表（用于联调、故障注入与回放）。
-- - 生产环境请执行迁移脚本：mall_sql/migrations/express_tracking_platform_upgrade.sql
--   该脚本仅创建平台侧 platform_express_* 三张表，并与主库 UUID/updated_at 规范对齐。
-- 目标：
-- 1) 平台侧：统一接收不同第三方事件 -> 幂等去重/乱序入库 -> 给前端提供稳定时间线
-- 2) Mock 承运方侧：可持久化运单/事件/场景/推送日志，支持故障注入与回放
-- 说明：本文件仅提供表结构与索引建议；RLS/权限策略按项目实际补充。

BEGIN;

-- Supabase / Postgres 常用扩展
CREATE EXTENSION IF NOT EXISTS pgcrypto;

-- 通用 updated_at 维护函数（如项目已有同名函数，可忽略重复定义）
CREATE OR REPLACE FUNCTION public.set_updated_at()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $$
BEGIN
  NEW.updated_at = NOW();
  RETURN NEW;
END;
$$;

-- =====================================================================
-- A. 平台侧（platform）：统一轨迹模型入库与查询
-- =====================================================================

CREATE TABLE IF NOT EXISTS platform_express_waybills (
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),

  -- 可选：若平台订单已存在，可关联
  order_id uuid NULL,
  order_no varchar(64) NULL,

  carrier varchar(32) NOT NULL,              -- YUNDA/YTO/ZTO/STO/KDN...
  tracking_no varchar(64) NOT NULL,
  source varchar(16) NOT NULL DEFAULT 'mock', -- mock/carrier/aggregator

  current_status_code varchar(32) NOT NULL DEFAULT 'SHIPPED',
  current_status_text text NULL,

  eta timestamptz NULL,
  last_synced_at timestamptz NULL,

  created_at timestamptz NOT NULL DEFAULT NOW(),
  updated_at timestamptz NOT NULL DEFAULT NOW(),

  CONSTRAINT uk_platform_waybill UNIQUE (carrier, tracking_no)
);

CREATE TRIGGER trg_platform_waybills_updated_at
BEFORE UPDATE ON platform_express_waybills
FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();

CREATE INDEX IF NOT EXISTS idx_platform_waybills_order_no ON platform_express_waybills(order_no);
CREATE INDEX IF NOT EXISTS idx_platform_waybills_tracking_no ON platform_express_waybills(tracking_no);
CREATE INDEX IF NOT EXISTS idx_platform_waybills_status ON platform_express_waybills(current_status_code);


-- 统一事件表：前端时间线/告警/统计的主数据来源
CREATE TABLE IF NOT EXISTS platform_express_tracking_events (
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),

  waybill_id uuid NOT NULL REFERENCES platform_express_waybills(id) ON DELETE CASCADE,
  carrier varchar(32) NOT NULL,
  tracking_no varchar(64) NOT NULL,

  -- 第三方事件唯一标识（可能缺失）
  event_id varchar(128) NULL,

  event_time timestamptz NOT NULL,
  event_code varchar(64) NOT NULL,
  event_text text NOT NULL,

  status_code varchar(32) NOT NULL,          -- 平台统一状态

  node_name varchar(128) NULL,
  location text NULL,
  description text NULL,

  evidence_urls jsonb NOT NULL DEFAULT '[]'::jsonb,

  -- 原始回文（用于审计/排障；如担心体积可移到 raw 表或只存引用）
  raw_payload jsonb NULL,

  -- 接收侧信息
  received_at timestamptz NOT NULL DEFAULT NOW(),
  source varchar(16) NOT NULL DEFAULT 'webhook', -- webhook/poll/manual

  -- 幂等去重键：优先 event_id；缺失时用 tracking_no+event_code+event_time(+可选字段) 构造
  dedupe_key varchar(256) NOT NULL,

  created_at timestamptz NOT NULL DEFAULT NOW(),

  CONSTRAINT uk_platform_event_dedupe UNIQUE (waybill_id, dedupe_key)
);

CREATE INDEX IF NOT EXISTS idx_platform_events_waybill_time ON platform_express_tracking_events(waybill_id, event_time);
CREATE INDEX IF NOT EXISTS idx_platform_events_tracking_time ON platform_express_tracking_events(tracking_no, event_time);
CREATE INDEX IF NOT EXISTS idx_platform_events_status ON platform_express_tracking_events(status_code);
CREATE INDEX IF NOT EXISTS idx_platform_events_received_at ON platform_express_tracking_events(received_at);
CREATE INDEX IF NOT EXISTS gin_platform_events_raw_payload ON platform_express_tracking_events USING gin (raw_payload);


-- 原始接收表：记录每一次 webhook/轮询原文、验签结果与解析错误
CREATE TABLE IF NOT EXISTS platform_express_event_raw (
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),

  received_at timestamptz NOT NULL DEFAULT NOW(),
  source varchar(16) NOT NULL DEFAULT 'webhook',

  -- 请求侧标识
  client_id varchar(64) NULL,
  carrier varchar(32) NULL,
  tracking_no varchar(64) NULL,

  -- 安全审计
  signature_valid boolean NULL,
  signature text NULL,
  ts_header text NULL,

  request_id varchar(64) NULL,
  remote_ip inet NULL,
  headers jsonb NULL,

  body jsonb NULL,
  parse_error text NULL,

  -- 去重辅助（可选）：用于识别完全重复的原始请求
  dedupe_key varchar(256) NULL
);

CREATE INDEX IF NOT EXISTS idx_platform_raw_received_at ON platform_express_event_raw(received_at);
CREATE INDEX IF NOT EXISTS idx_platform_raw_tracking_no ON platform_express_event_raw(tracking_no);
CREATE INDEX IF NOT EXISTS idx_platform_raw_signature_valid ON platform_express_event_raw(signature_valid);
CREATE INDEX IF NOT EXISTS gin_platform_raw_body ON platform_express_event_raw USING gin (body);


-- =====================================================================
-- B. Mock 承运方侧（mock）：运单/事件/场景/推送日志
-- =====================================================================

CREATE TABLE IF NOT EXISTS mock_carrier_config (
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),

  name varchar(64) NOT NULL DEFAULT 'default',
  target_webhook_url text NULL,

  client_id varchar(64) NULL,
  secret text NULL,
  default_carrier varchar(32) NOT NULL DEFAULT 'YUNDA',

  is_active boolean NOT NULL DEFAULT true,

  created_at timestamptz NOT NULL DEFAULT NOW(),
  updated_at timestamptz NOT NULL DEFAULT NOW(),

  CONSTRAINT uk_mock_config_name UNIQUE (name)
);

CREATE TRIGGER trg_mock_carrier_config_updated_at
BEFORE UPDATE ON mock_carrier_config
FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();


CREATE TABLE IF NOT EXISTS mock_waybills (
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),

  carrier varchar(32) NOT NULL,
  tracking_no varchar(64) NOT NULL,
  order_no varchar(64) NULL,

  created_at timestamptz NOT NULL DEFAULT NOW(),
  updated_at timestamptz NOT NULL DEFAULT NOW(),

  CONSTRAINT uk_mock_waybill UNIQUE (carrier, tracking_no)
);

CREATE TRIGGER trg_mock_waybills_updated_at
BEFORE UPDATE ON mock_waybills
FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();


CREATE TABLE IF NOT EXISTS mock_tracking_events (
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),

  waybill_id uuid NOT NULL REFERENCES mock_waybills(id) ON DELETE CASCADE,
  carrier varchar(32) NOT NULL,
  tracking_no varchar(64) NOT NULL,

  event_id varchar(128) NULL,
  event_time timestamptz NOT NULL,
  event_code varchar(64) NOT NULL,
  event_text text NOT NULL,

  node_name varchar(128) NULL,
  location text NULL,
  description text NULL,
  evidence_urls jsonb NOT NULL DEFAULT '[]'::jsonb,

  raw_payload jsonb NULL,

  -- 生成幂等键，便于 Mock 内部也能去重
  dedupe_key varchar(256) NOT NULL,

  created_at timestamptz NOT NULL DEFAULT NOW(),

  CONSTRAINT uk_mock_event_dedupe UNIQUE (waybill_id, dedupe_key)
);

CREATE INDEX IF NOT EXISTS idx_mock_events_waybill_time ON mock_tracking_events(waybill_id, event_time);


-- 场景定义：标准签收/拒收退回/地址异常等
CREATE TABLE IF NOT EXISTS mock_scenarios (
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  scenario_code varchar(64) NOT NULL,
  scenario_name varchar(128) NOT NULL,
  description text NULL,

  is_active boolean NOT NULL DEFAULT true,

  created_at timestamptz NOT NULL DEFAULT NOW(),
  updated_at timestamptz NOT NULL DEFAULT NOW(),

  CONSTRAINT uk_mock_scenario_code UNIQUE (scenario_code)
);

CREATE TRIGGER trg_mock_scenarios_updated_at
BEFORE UPDATE ON mock_scenarios
FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();


-- 场景步骤：相对时间偏移 + 事件内容
CREATE TABLE IF NOT EXISTS mock_scenario_steps (
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  scenario_id uuid NOT NULL REFERENCES mock_scenarios(id) ON DELETE CASCADE,

  step_no int NOT NULL,
  offset_seconds int NOT NULL DEFAULT 0,

  event_code varchar(64) NOT NULL,
  event_text text NOT NULL,
  status_code varchar(32) NULL,

  node_name varchar(128) NULL,
  location text NULL,
  description text NULL,
  evidence_urls jsonb NOT NULL DEFAULT '[]'::jsonb,

  created_at timestamptz NOT NULL DEFAULT NOW(),

  CONSTRAINT uk_mock_scenario_step UNIQUE (scenario_id, step_no)
);


-- 场景运行记录：便于复现/回放
CREATE TABLE IF NOT EXISTS mock_scenario_runs (
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),

  waybill_id uuid NOT NULL REFERENCES mock_waybills(id) ON DELETE CASCADE,
  scenario_id uuid NOT NULL REFERENCES mock_scenarios(id),

  inject jsonb NOT NULL DEFAULT '{}'::jsonb, -- delay_ms/duplicate/out_of_order/bad_signature/timestamp_skew_seconds/drop_fields...
  pushed boolean NOT NULL DEFAULT false,

  started_at timestamptz NOT NULL DEFAULT NOW(),
  finished_at timestamptz NULL
);

CREATE INDEX IF NOT EXISTS idx_mock_runs_waybill ON mock_scenario_runs(waybill_id, started_at);


-- 推送日志：记录每次 push 的请求/响应摘要
CREATE TABLE IF NOT EXISTS mock_push_logs (
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),

  waybill_id uuid NOT NULL REFERENCES mock_waybills(id) ON DELETE CASCADE,
  event_id varchar(128) NULL,
  dedupe_key varchar(256) NULL,

  target_url text NULL,
  request_headers jsonb NULL,
  request_body jsonb NULL,

  response_status int NULL,
  response_body text NULL,
  error_message text NULL,

  created_at timestamptz NOT NULL DEFAULT NOW()
);

CREATE INDEX IF NOT EXISTS idx_mock_push_waybill ON mock_push_logs(waybill_id, created_at);

COMMIT;
