-- ============================================================================
-- affiliate_dash 初始化脚本（唯一迁移文件）
-- 全平台统一使用积分（POINT）作为结算货币；商户手续费为"百分比或固定"二选一。
-- 本脚本幂等：所有 CREATE 均带 IF NOT EXISTS，种子数据均用 ON CONFLICT DO NOTHING。
-- ============================================================================

-- ----------------------------------------------------------------------------
-- 用户与商户
-- ----------------------------------------------------------------------------

CREATE TABLE IF NOT EXISTS users (
    id BIGSERIAL PRIMARY KEY,
    created_at TIMESTAMPTZ,
    updated_at TIMESTAMPTZ,
    deleted_at TIMESTAMPTZ,
    username VARCHAR(64) NOT NULL,
    password_hash VARCHAR(255) NOT NULL,
    nickname VARCHAR(64),
    role VARCHAR(32) NOT NULL DEFAULT 'merchant',
    status BIGINT DEFAULT 1
);

CREATE UNIQUE INDEX IF NOT EXISTS idx_users_username ON users (username);
CREATE INDEX IF NOT EXISTS idx_users_deleted_at ON users (deleted_at);

CREATE TABLE IF NOT EXISTS merchants (
    id BIGSERIAL PRIMARY KEY,
    created_at TIMESTAMPTZ,
    updated_at TIMESTAMPTZ,
    deleted_at TIMESTAMPTZ,
    code VARCHAR(64) NOT NULL,
    name VARCHAR(128) NOT NULL,
    status VARCHAR(16) NOT NULL DEFAULT 'active',
    contact_name VARCHAR(64),
    contact_info VARCHAR(128),
    features TEXT NOT NULL DEFAULT 'products,orders,wallet,api,callbacks',
    fee_type VARCHAR(16) NOT NULL DEFAULT 'rate',
    fee_rate_bp BIGINT NOT NULL DEFAULT 0,
    fee_fixed_amount BIGINT NOT NULL DEFAULT 0
);

CREATE UNIQUE INDEX IF NOT EXISTS idx_merchants_code ON merchants (code);
CREATE INDEX IF NOT EXISTS idx_merchants_deleted_at ON merchants (deleted_at);
CREATE INDEX IF NOT EXISTS idx_merchants_status ON merchants (status);

CREATE TABLE IF NOT EXISTS merchant_members (
    id BIGSERIAL PRIMARY KEY,
    created_at TIMESTAMPTZ,
    updated_at TIMESTAMPTZ,
    merchant_id BIGINT NOT NULL,
    user_id BIGINT NOT NULL,
    role VARCHAR(32) NOT NULL DEFAULT 'operator',
    status BIGINT NOT NULL DEFAULT 1,
    is_default BOOLEAN NOT NULL DEFAULT FALSE
);

CREATE UNIQUE INDEX IF NOT EXISTS idx_merchant_member ON merchant_members (merchant_id, user_id);
CREATE INDEX IF NOT EXISTS idx_merchant_members_merchant_id ON merchant_members (merchant_id);
CREATE INDEX IF NOT EXISTS idx_merchant_members_user_id ON merchant_members (user_id);

-- ----------------------------------------------------------------------------
-- 开放接口凭证
-- ----------------------------------------------------------------------------

CREATE TABLE IF NOT EXISTS api_clients (
    id BIGSERIAL PRIMARY KEY,
    created_at TIMESTAMPTZ,
    updated_at TIMESTAMPTZ,
    deleted_at TIMESTAMPTZ,
    merchant_id BIGINT NOT NULL,
    name VARCHAR(128) NOT NULL,
    app_key VARCHAR(96) NOT NULL,
    secret_ciphertext TEXT NOT NULL,
    signature_version VARCHAR(16) NOT NULL DEFAULT 'v1',
    scopes TEXT NOT NULL,
    status VARCHAR(16) NOT NULL DEFAULT 'active',
    expires_at TIMESTAMPTZ,
    last_used_at TIMESTAMPTZ
);

CREATE UNIQUE INDEX IF NOT EXISTS idx_api_clients_app_key ON api_clients (app_key);
CREATE INDEX IF NOT EXISTS idx_api_clients_deleted_at ON api_clients (deleted_at);
CREATE INDEX IF NOT EXISTS idx_api_clients_merchant_id ON api_clients (merchant_id);
CREATE INDEX IF NOT EXISTS idx_api_clients_status ON api_clients (status);

CREATE TABLE IF NOT EXISTS api_request_nonces (
    id BIGSERIAL PRIMARY KEY,
    created_at TIMESTAMPTZ,
    api_client_id BIGINT NOT NULL,
    nonce VARCHAR(96) NOT NULL,
    expires_at TIMESTAMPTZ NOT NULL
);

CREATE UNIQUE INDEX IF NOT EXISTS idx_api_nonce ON api_request_nonces (api_client_id, nonce);
CREATE INDEX IF NOT EXISTS idx_api_request_nonces_api_client_id ON api_request_nonces (api_client_id);
CREATE INDEX IF NOT EXISTS idx_api_request_nonces_expires_at ON api_request_nonces (expires_at);

-- ----------------------------------------------------------------------------
-- 商品目录与商户可售商品
-- ----------------------------------------------------------------------------

CREATE TABLE IF NOT EXISTS products (
    id BIGSERIAL PRIMARY KEY,
    created_at TIMESTAMPTZ,
    updated_at TIMESTAMPTZ,
    deleted_at TIMESTAMPTZ,
    code VARCHAR(96) NOT NULL,
    name VARCHAR(160) NOT NULL,
    category VARCHAR(64),
    description TEXT,
    attributes TEXT,
    status VARCHAR(16) NOT NULL DEFAULT 'active'
);

CREATE UNIQUE INDEX IF NOT EXISTS idx_products_code ON products (code);
CREATE INDEX IF NOT EXISTS idx_products_deleted_at ON products (deleted_at);
CREATE INDEX IF NOT EXISTS idx_products_category ON products (category);
CREATE INDEX IF NOT EXISTS idx_products_status ON products (status);

CREATE TABLE IF NOT EXISTS merchant_products (
    id BIGSERIAL PRIMARY KEY,
    created_at TIMESTAMPTZ,
    updated_at TIMESTAMPTZ,
    deleted_at TIMESTAMPTZ,
    merchant_id BIGINT NOT NULL,
    product_id BIGINT NOT NULL,
    sku VARCHAR(96) NOT NULL,
    display_name VARCHAR(160),
    price_amount BIGINT NOT NULL DEFAULT 0,
    cost_amount BIGINT NOT NULL DEFAULT 0,
    currency VARCHAR(12) NOT NULL DEFAULT 'POINT',
    stock BIGINT NOT NULL DEFAULT -1,
    status VARCHAR(16) NOT NULL DEFAULT 'active',
    fulfillment_config TEXT
);

CREATE UNIQUE INDEX IF NOT EXISTS idx_merchant_product_sku ON merchant_products (merchant_id, sku);
CREATE INDEX IF NOT EXISTS idx_merchant_products_deleted_at ON merchant_products (deleted_at);
CREATE INDEX IF NOT EXISTS idx_merchant_products_merchant_id ON merchant_products (merchant_id);
CREATE INDEX IF NOT EXISTS idx_merchant_products_product_id ON merchant_products (product_id);
CREATE INDEX IF NOT EXISTS idx_merchant_products_status ON merchant_products (status);

-- ----------------------------------------------------------------------------
-- 钱包账户与流水
-- ----------------------------------------------------------------------------

CREATE TABLE IF NOT EXISTS wallet_accounts (
    id BIGSERIAL PRIMARY KEY,
    created_at TIMESTAMPTZ,
    updated_at TIMESTAMPTZ,
    merchant_id BIGINT NOT NULL,
    currency VARCHAR(12) NOT NULL DEFAULT 'POINT',
    available_balance BIGINT NOT NULL DEFAULT 0,
    frozen_balance BIGINT NOT NULL DEFAULT 0
);

CREATE UNIQUE INDEX IF NOT EXISTS idx_wallet_accounts_merchant_id ON wallet_accounts (merchant_id);

CREATE TABLE IF NOT EXISTS wallet_ledger_entries (
    id BIGSERIAL PRIMARY KEY,
    created_at TIMESTAMPTZ,
    merchant_id BIGINT NOT NULL,
    wallet_account_id BIGINT NOT NULL,
    entry_no VARCHAR(64) NOT NULL,
    type VARCHAR(24) NOT NULL,
    amount BIGINT NOT NULL,
    balance_after BIGINT NOT NULL,
    reference_type VARCHAR(32) NOT NULL,
    reference_no VARCHAR(96) NOT NULL,
    idempotency_key VARCHAR(128),
    note VARCHAR(255)
);

CREATE UNIQUE INDEX IF NOT EXISTS idx_wallet_ledger_entries_entry_no ON wallet_ledger_entries (entry_no);
CREATE UNIQUE INDEX IF NOT EXISTS idx_wallet_ledger_idempotency ON wallet_ledger_entries (merchant_id, idempotency_key);
CREATE INDEX IF NOT EXISTS idx_wallet_ledger_entries_merchant_id ON wallet_ledger_entries (merchant_id);
CREATE INDEX IF NOT EXISTS idx_wallet_ledger_entries_wallet_account_id ON wallet_ledger_entries (wallet_account_id);
CREATE INDEX IF NOT EXISTS idx_wallet_ledger_entries_type ON wallet_ledger_entries (type);
CREATE INDEX IF NOT EXISTS idx_wallet_ledger_entries_reference_no ON wallet_ledger_entries (reference_no);

-- ----------------------------------------------------------------------------
-- 履约订单与任务
-- ----------------------------------------------------------------------------

CREATE TABLE IF NOT EXISTS fulfillment_orders (
    id BIGSERIAL PRIMARY KEY,
    created_at TIMESTAMPTZ,
    updated_at TIMESTAMPTZ,
    deleted_at TIMESTAMPTZ,
    merchant_id BIGINT NOT NULL,
    order_no VARCHAR(64) NOT NULL,
    client_order_no VARCHAR(96) NOT NULL,
    merchant_product_id BIGINT NOT NULL,
    product_sku VARCHAR(96) NOT NULL,
    product_name VARCHAR(160) NOT NULL,
    quantity BIGINT NOT NULL DEFAULT 1,
    base_amount BIGINT NOT NULL DEFAULT 0,
    fee_type VARCHAR(16) NOT NULL DEFAULT 'rate',
    fee_rate_bp BIGINT NOT NULL DEFAULT 0,
    fee_fixed_amount BIGINT NOT NULL DEFAULT 0,
    service_fee_amount BIGINT NOT NULL DEFAULT 0,
    amount BIGINT NOT NULL,
    currency VARCHAR(12) NOT NULL DEFAULT 'POINT',
    payment_status VARCHAR(16) NOT NULL DEFAULT 'pending',
    fulfillment_status VARCHAR(16) NOT NULL DEFAULT 'pending',
    buyer_reference VARCHAR(128),
    request_data TEXT,
    result_data TEXT,
    provider_order_no VARCHAR(96),
    failure_reason VARCHAR(512),
    cancelled_at TIMESTAMPTZ,
    delivered_at TIMESTAMPTZ
);

CREATE UNIQUE INDEX IF NOT EXISTS idx_fulfillment_orders_order_no ON fulfillment_orders (order_no);
CREATE UNIQUE INDEX IF NOT EXISTS idx_order_client_no ON fulfillment_orders (merchant_id, client_order_no);
CREATE INDEX IF NOT EXISTS idx_fulfillment_orders_deleted_at ON fulfillment_orders (deleted_at);
CREATE INDEX IF NOT EXISTS idx_fulfillment_orders_merchant_id ON fulfillment_orders (merchant_id);
CREATE INDEX IF NOT EXISTS idx_fulfillment_orders_merchant_product_id ON fulfillment_orders (merchant_product_id);
CREATE INDEX IF NOT EXISTS idx_fulfillment_orders_payment_status ON fulfillment_orders (payment_status);
CREATE INDEX IF NOT EXISTS idx_fulfillment_orders_fulfillment_status ON fulfillment_orders (fulfillment_status);
CREATE INDEX IF NOT EXISTS idx_fulfillment_orders_provider_order_no ON fulfillment_orders (provider_order_no);

CREATE TABLE IF NOT EXISTS fulfillment_jobs (
    id BIGSERIAL PRIMARY KEY,
    created_at TIMESTAMPTZ,
    updated_at TIMESTAMPTZ,
    merchant_id BIGINT NOT NULL,
    order_id BIGINT NOT NULL,
    status VARCHAR(16) NOT NULL DEFAULT 'pending',
    attempts BIGINT NOT NULL DEFAULT 0,
    next_run_at TIMESTAMPTZ NOT NULL,
    provider_order_no VARCHAR(96),
    request_payload TEXT,
    result_payload TEXT,
    last_error VARCHAR(512)
);

CREATE UNIQUE INDEX IF NOT EXISTS idx_fulfillment_jobs_order_id ON fulfillment_jobs (order_id);
CREATE INDEX IF NOT EXISTS idx_fulfillment_jobs_merchant_id ON fulfillment_jobs (merchant_id);
CREATE INDEX IF NOT EXISTS idx_fulfillment_jobs_status ON fulfillment_jobs (status);
CREATE INDEX IF NOT EXISTS idx_fulfillment_jobs_next_run_at ON fulfillment_jobs (next_run_at);
CREATE INDEX IF NOT EXISTS idx_fulfillment_jobs_provider_order_no ON fulfillment_jobs (provider_order_no);

-- ----------------------------------------------------------------------------
-- 回调订阅与投递（事务性 outbox）
-- ----------------------------------------------------------------------------

CREATE TABLE IF NOT EXISTS callback_subscriptions (
    id BIGSERIAL PRIMARY KEY,
    created_at TIMESTAMPTZ,
    updated_at TIMESTAMPTZ,
    deleted_at TIMESTAMPTZ,
    merchant_id BIGINT NOT NULL,
    name VARCHAR(128) NOT NULL,
    url VARCHAR(1024) NOT NULL,
    events TEXT NOT NULL,
    secret_ciphertext TEXT NOT NULL,
    status VARCHAR(16) NOT NULL DEFAULT 'active'
);

CREATE INDEX IF NOT EXISTS idx_callback_subscriptions_deleted_at ON callback_subscriptions (deleted_at);
CREATE INDEX IF NOT EXISTS idx_callback_subscriptions_merchant_id ON callback_subscriptions (merchant_id);
CREATE INDEX IF NOT EXISTS idx_callback_subscriptions_status ON callback_subscriptions (status);

CREATE TABLE IF NOT EXISTS callback_deliveries (
    id BIGSERIAL PRIMARY KEY,
    created_at TIMESTAMPTZ,
    updated_at TIMESTAMPTZ,
    merchant_id BIGINT NOT NULL,
    callback_subscription_id BIGINT NOT NULL,
    event_id VARCHAR(64) NOT NULL,
    event VARCHAR(64) NOT NULL,
    payload TEXT NOT NULL,
    status VARCHAR(16) NOT NULL DEFAULT 'pending',
    attempts BIGINT NOT NULL DEFAULT 0,
    next_attempt_at TIMESTAMPTZ NOT NULL,
    last_status_code BIGINT NOT NULL DEFAULT 0,
    last_response TEXT,
    delivered_at TIMESTAMPTZ
);

CREATE UNIQUE INDEX IF NOT EXISTS idx_callback_deliveries_event_id ON callback_deliveries (event_id);
CREATE INDEX IF NOT EXISTS idx_callback_deliveries_merchant_id ON callback_deliveries (merchant_id);
CREATE INDEX IF NOT EXISTS idx_callback_deliveries_callback_subscription_id ON callback_deliveries (callback_subscription_id);
CREATE INDEX IF NOT EXISTS idx_callback_deliveries_event ON callback_deliveries (event);
CREATE INDEX IF NOT EXISTS idx_callback_deliveries_status ON callback_deliveries (status);
CREATE INDEX IF NOT EXISTS idx_callback_deliveries_next_attempt_at ON callback_deliveries (next_attempt_at);

-- ----------------------------------------------------------------------------
-- 审计日志
-- ----------------------------------------------------------------------------

CREATE TABLE IF NOT EXISTS audit_logs (
    id BIGSERIAL PRIMARY KEY,
    created_at TIMESTAMPTZ,
    merchant_id BIGINT,
    actor_user_id BIGINT,
    api_client_id BIGINT,
    request_id VARCHAR(64),
    action VARCHAR(96) NOT NULL,
    entity_type VARCHAR(64) NOT NULL,
    entity_id VARCHAR(96) NOT NULL,
    metadata TEXT
);

CREATE INDEX IF NOT EXISTS idx_audit_logs_merchant_id ON audit_logs (merchant_id);
CREATE INDEX IF NOT EXISTS idx_audit_logs_actor_user_id ON audit_logs (actor_user_id);
CREATE INDEX IF NOT EXISTS idx_audit_logs_api_client_id ON audit_logs (api_client_id);
CREATE INDEX IF NOT EXISTS idx_audit_logs_request_id ON audit_logs (request_id);
CREATE INDEX IF NOT EXISTS idx_audit_logs_action ON audit_logs (action);
CREATE INDEX IF NOT EXISTS idx_audit_logs_entity_type ON audit_logs (entity_type);
CREATE INDEX IF NOT EXISTS idx_audit_logs_entity_id ON audit_logs (entity_id);

-- ============================================================================
-- 种子数据
-- ============================================================================

-- 1. 自营商户（平台默认租户，供 EnsureAdmin 关联成员）
INSERT INTO merchants (code, name, status, features, fee_type)
VALUES ('self-operated', '自营商户', 'active', 'products,orders,wallet,api,callbacks', 'rate')
ON CONFLICT (code) DO NOTHING;

-- 2. 自营商户积分钱包
INSERT INTO wallet_accounts (merchant_id, currency)
SELECT id, 'POINT' FROM merchants WHERE code = 'self-operated'
ON CONFLICT (merchant_id) DO NOTHING;

-- 3. 平台商品目录（和平精英商品，幂等）
INSERT INTO products (code, name, category, status)
SELECT v.code, v.name, v.category, 'active'
FROM (VALUES
  ('suit_alan_walker',       '套装-Alan Walker',         '套装'),
  ('suit_shadow_gothic',     '套装-暗影哥特',             '套装'),
  ('top_black_elite_trainer','黑色高级特训官上衣',        '上衣'),
  ('m416_hamster_gray',      'M416-仓鼠灰灰',             '枪械'),
  ('bag_cute_bear',          '萌熊伴侣背包',              '背包'),
  ('suit_dual_fluffy',       '套装-双彩绵绵',             '套装'),
  ('suit_pink_sheep',        '套装-糯粉咩咩',             '套装'),
  ('suit_first_peach',       '套装-恋恋初桃',             '套装'),
  ('suit_romantic_destiny',  '套装-浪漫天命',             '套装'),
  ('pack_western_cowboy',    '西部牛仔大礼包',            '礼包'),
  ('smoke_pink_sheep',       '烟雾弹-糯粉咩咩',           '投掷物'),
  ('frag_pink_sheep',        '破片手榴弹-糯粉咩咩',       '投掷物'),
  ('suit_hamster_gray',      '套装-仓鼠灰灰',             '套装'),
  ('suit_cute_bear',         '套装-萌熊伴侣',             '套装'),
  ('bag_pink_sheep',         '糯粉咩咩背包',              '背包'),
  ('helmet_pink_sheep',      '糯粉咩咩头盔',              '头盔'),
  ('bag_hamster_gray',       '仓鼠灰灰背包',              '背包'),
  ('helmet_hamster_gray',    '仓鼠灰灰头盔',              '头盔'),
  ('suit_western_mystery',   '套装-西部谜踪',             '套装'),
  ('helmet_panda_treasure',  '国宝胖达头盔',              '头盔'),
  ('suit_panda_round',       '套装-胖达圆圆',             '套装'),
  ('suit_panda_tuan',        '套装-胖达团团',             '套装'),
  ('pack_lava_ranger',       '熔岩游骑兵礼包',            '礼包'),
  ('suit_sand_dancer',       '套装-狂沙舞者',             '套装'),
  ('pack_star_roam_outfit', '星际漫游服装礼包',          '礼包'),
  ('pack_star_roam_weapon',  '星际漫游枪械礼包',          '礼包'),
  ('suit_cloud_bear',        '套装-绵云熊熊',             '套装')
) AS v(code, name, category)
ON CONFLICT (code) DO NOTHING;

-- 4. 自营商户可售商品（价格一步到位，幂等，按 merchant_id + sku 唯一索引）
INSERT INTO merchant_products (merchant_id, product_id, sku, display_name, price_amount, cost_amount, currency, stock, status)
SELECT
  m.id,
  p.id,
  p.code,
  p.name,
  v.price_amount,
  v.cost_amount,
  'POINT',
  -1,
  'active'
FROM (VALUES
  ('suit_alan_walker',       360,  360),
  ('suit_shadow_gothic',     888,  888),
  ('top_black_elite_trainer',360,  360),
  ('m416_hamster_gray',      1680, 1680),
  ('bag_cute_bear',          2880, 2880),
  ('suit_dual_fluffy',       1124, 1124),
  ('suit_pink_sheep',        1880, 1880),
  ('suit_first_peach',       988,  988),
  ('suit_romantic_destiny',  360,  360),
  ('pack_western_cowboy',    3200, 3200),
  ('smoke_pink_sheep',       488,  488),
  ('frag_pink_sheep',        488,  488),
  ('suit_hamster_gray',      1280, 1280),
  ('suit_cute_bear',         1680, 1680),
  ('bag_pink_sheep',         1680, 1680),
  ('helmet_pink_sheep',      1680, 1680),
  ('bag_hamster_gray',       1680, 1680),
  ('helmet_hamster_gray',    1680, 1680),
  ('suit_western_mystery',   1150, 1150),
  ('helmet_panda_treasure',  2340, 2340),
  ('suit_panda_round',       2340, 2340),
  ('suit_panda_tuan',        2340, 2340),
  ('pack_lava_ranger',       960,  960),
  ('suit_sand_dancer',       840,  840),
  ('pack_star_roam_outfit',  8360, 8360),
  ('pack_star_roam_weapon',  5160, 5160),
  ('suit_cloud_bear',        1348, 1348)
) AS v(sku, price_amount, cost_amount)
JOIN products p ON p.code = v.sku
JOIN merchants m ON m.code = 'self-operated'
ON CONFLICT (merchant_id, sku) DO NOTHING;
