create table if not exists public.referral_codes (
  id uuid primary key default gen_random_uuid(),
  tenant_id uuid not null references public.tenants(id) on delete cascade,
  user_id uuid not null references public.platform_users(id) on delete cascade,
  code citext not null,
  status text not null default 'active' check (status in ('active', 'disabled')),
  channel text,
  landing_path text,
  metadata jsonb not null default '{}'::jsonb,
  created_at timestamptz not null default now(),
  updated_at timestamptz not null default now(),
  unique (tenant_id, user_id),
  unique (tenant_id, code)
);

comment on table public.referral_codes is
  'Tenant-scoped referral codes for sales, agents, teachers, operators, and other permitted referrers.';

create table if not exists public.referral_leads (
  id uuid primary key default gen_random_uuid(),
  tenant_id uuid not null references public.tenants(id) on delete cascade,
  student_user_id uuid not null references public.platform_users(id) on delete cascade,
  referrer_user_id uuid references public.platform_users(id) on delete set null,
  ref_code citext,
  source text,
  first_track_id uuid references public.referral_tracks(id) on delete set null,
  bind_type text not null default 'first_touch' check (bind_type in ('first_touch', 'manual', 'imported')),
  status text not null default 'protected' check (status in ('protected', 'invalid', 'released')),
  protected_until timestamptz,
  metadata jsonb not null default '{}'::jsonb,
  bound_at timestamptz not null default now(),
  created_at timestamptz not null default now(),
  updated_at timestamptz not null default now(),
  unique (tenant_id, student_user_id)
);

comment on table public.referral_leads is
  'First-binding lead ownership. Once a student is bound to a referrer, normal scan/share events cannot rebind the lead.';

create table if not exists public.referral_team_edges (
  id uuid primary key default gen_random_uuid(),
  tenant_id uuid not null references public.tenants(id) on delete cascade,
  member_user_id uuid not null references public.platform_users(id) on delete cascade,
  leader_user_id uuid references public.platform_users(id) on delete set null,
  relation_type text not null default 'sales_team' check (relation_type in ('sales_team', 'agent_network', 'teacher_class')),
  status text not null default 'active' check (status in ('active', 'disabled')),
  metadata jsonb not null default '{}'::jsonb,
  created_at timestamptz not null default now(),
  updated_at timestamptz not null default now(),
  unique (tenant_id, member_user_id, relation_type)
);

create table if not exists public.referral_qrcodes (
  id uuid primary key default gen_random_uuid(),
  tenant_id uuid not null references public.tenants(id) on delete cascade,
  user_id uuid references public.platform_users(id) on delete set null,
  ref_code citext not null,
  scene text not null,
  page text not null default 'pages/index/index',
  provider text not null default 'wechat-miniapp',
  qrcode_url text,
  status text not null default 'pending' check (status in ('pending', 'ready', 'failed', 'disabled')),
  error_message text,
  metadata jsonb not null default '{}'::jsonb,
  created_at timestamptz not null default now(),
  updated_at timestamptz not null default now(),
  unique (tenant_id, provider, scene, page)
);

alter table public.referral_tracks
  add column if not exists metadata jsonb not null default '{}'::jsonb,
  add column if not exists lead_id uuid references public.referral_leads(id) on delete set null;

alter table public.crm_webhook_queue
  add column if not exists source text,
  add column if not exists payload jsonb not null default '{}'::jsonb,
  add column if not exists idempotency_key text,
  add column if not exists target_url text;

alter table public.crm_webhook_log
  add column if not exists request_payload jsonb not null default '{}'::jsonb;

create unique index if not exists idx_crm_queue_tenant_record_id
  on public.crm_webhook_queue(tenant_id, record_id)
  where record_id is not null;

create unique index if not exists idx_crm_queue_tenant_idempotency
  on public.crm_webhook_queue(tenant_id, idempotency_key)
  where idempotency_key is not null;

create index if not exists idx_referral_codes_tenant_code
  on public.referral_codes(tenant_id, code, status);

create index if not exists idx_referral_leads_referrer
  on public.referral_leads(tenant_id, referrer_user_id, bound_at desc);

create index if not exists idx_referral_team_leader
  on public.referral_team_edges(tenant_id, leader_user_id, status);

create index if not exists idx_referral_tracks_lead
  on public.referral_tracks(tenant_id, lead_id, created_at desc);

alter table public.referral_codes enable row level security;
alter table public.referral_leads enable row level security;
alter table public.referral_team_edges enable row level security;
alter table public.referral_qrcodes enable row level security;

drop policy if exists tenant_isolation on public.referral_codes;
create policy tenant_isolation on public.referral_codes
  for all
  using (tenant_id = app.current_tenant_id() or app.is_platform_admin())
  with check (tenant_id = app.current_tenant_id() or app.is_platform_admin());

drop policy if exists tenant_isolation on public.referral_leads;
create policy tenant_isolation on public.referral_leads
  for all
  using (tenant_id = app.current_tenant_id() or app.is_platform_admin())
  with check (tenant_id = app.current_tenant_id() or app.is_platform_admin());

drop policy if exists tenant_isolation on public.referral_team_edges;
create policy tenant_isolation on public.referral_team_edges
  for all
  using (tenant_id = app.current_tenant_id() or app.is_platform_admin())
  with check (tenant_id = app.current_tenant_id() or app.is_platform_admin());

drop policy if exists tenant_isolation on public.referral_qrcodes;
create policy tenant_isolation on public.referral_qrcodes
  for all
  using (tenant_id = app.current_tenant_id() or app.is_platform_admin())
  with check (tenant_id = app.current_tenant_id() or app.is_platform_admin());

drop trigger if exists set_updated_at on public.referral_codes;
create trigger set_updated_at
  before update on public.referral_codes
  for each row execute function app.touch_updated_at();

drop trigger if exists set_updated_at on public.referral_leads;
create trigger set_updated_at
  before update on public.referral_leads
  for each row execute function app.touch_updated_at();

drop trigger if exists set_updated_at on public.referral_team_edges;
create trigger set_updated_at
  before update on public.referral_team_edges
  for each row execute function app.touch_updated_at();

drop trigger if exists set_updated_at on public.referral_qrcodes;
create trigger set_updated_at
  before update on public.referral_qrcodes
  for each row execute function app.touch_updated_at();
