alter table public.credit_packs
  add column if not exists alipay_amount_cents integer
  check (alipay_amount_cents is null or alipay_amount_cents > 0);

alter table public.payment_orders
  add column if not exists payment_provider text not null default 'stripe',
  add column if not exists provider_trade_no text,
  add column if not exists provider_refund_request_no text;

alter table public.payment_orders
  drop constraint if exists payment_orders_payment_provider_check;

alter table public.payment_orders
  add constraint payment_orders_payment_provider_check
  check (payment_provider in ('stripe', 'alipay'));

alter table public.payment_orders
  drop constraint if exists payment_orders_status_check;

alter table public.payment_orders
  add constraint payment_orders_status_check
  check (status in (
    'created',
    'checkout_created',
    'completed',
    'failed',
    'canceled',
    'refund_pending',
    'refunded'
  ));

create unique index if not exists payment_orders_provider_trade_no_idx
  on public.payment_orders (payment_provider, provider_trade_no)
  where provider_trade_no is not null;

create unique index if not exists payment_orders_provider_refund_request_no_idx
  on public.payment_orders (payment_provider, provider_refund_request_no)
  where provider_refund_request_no is not null;

create table if not exists public.alipay_notify_events (
  id bigint generated by default as identity primary key,
  order_id uuid not null references public.payment_orders(id) on delete cascade,
  notify_id text not null unique,
  trade_no text not null,
  trade_status text not null,
  payload jsonb not null default '{}'::jsonb,
  created_at timestamptz not null default now()
);

create unique index if not exists alipay_notify_order_trade_event_idx
  on public.alipay_notify_events (order_id, trade_no, trade_status, notify_id);

alter table public.alipay_notify_events enable row level security;

revoke all on table public.alipay_notify_events from public, anon, authenticated;
grant all on table public.alipay_notify_events to service_role;
grant usage, select on sequence public.alipay_notify_events_id_seq to service_role;

drop function if exists public.complete_alipay_credit_pack_order(uuid, text, integer);

create or replace function public.complete_alipay_credit_pack_order(
  p_order_id uuid,
  p_trade_no text,
  p_paid_amount_cents integer,
  p_notify_id text default null,
  p_notify_payload jsonb default '{}'::jsonb
)
returns table (
  completed boolean,
  credit_balance integer
)
language plpgsql
security definer
set search_path = public
as $$
declare
  v_order public.payment_orders%rowtype;
  v_balance integer;
begin
  select po.*
    into v_order
    from public.payment_orders as po
   where po.id = p_order_id
   for update;

  if not found then
    raise exception 'PAYMENT_ORDER_NOT_FOUND' using errcode = 'P0001';
  end if;

  if v_order.payment_provider <> 'alipay' or v_order.product_type <> 'credit_pack' then
    raise exception 'INVALID_ALIPAY_ORDER' using errcode = 'P0001';
  end if;

  if p_trade_no is null or btrim(p_trade_no) = '' then
    raise exception 'ALIPAY_TRADE_NO_REQUIRED' using errcode = 'P0001';
  end if;

  if p_paid_amount_cents <> v_order.amount_cents then
    raise exception 'ALIPAY_AMOUNT_MISMATCH' using errcode = 'P0001';
  end if;

  if p_notify_id is not null and btrim(p_notify_id) <> '' then
    insert into public.alipay_notify_events (
      order_id,
      notify_id,
      trade_no,
      trade_status,
      payload
    )
    values (
      v_order.id,
      p_notify_id,
      p_trade_no,
      coalesce(nullif(p_notify_payload ->> 'trade_status', ''), 'TRADE_SUCCESS'),
      coalesce(p_notify_payload, '{}'::jsonb)
    )
    on conflict (notify_id) do nothing;
  end if;

  if v_order.status = 'completed' then
    if v_order.provider_trade_no is not null and v_order.provider_trade_no <> p_trade_no then
      raise exception 'ALIPAY_TRADE_NO_MISMATCH' using errcode = 'P0001';
    end if;

    select p.credit_balance
      into v_balance
      from public.profiles as p
     where p.id = v_order.user_id;

    return query select false, v_balance;
    return;
  end if;

  if v_order.status not in ('created', 'checkout_created') then
    raise exception 'ALIPAY_ORDER_NOT_COMPLETABLE' using errcode = 'P0001';
  end if;

  update public.profiles as p
     set credit_balance = p.credit_balance + v_order.credits
   where p.id = v_order.user_id
   returning p.credit_balance into v_balance;

  if not found then
    raise exception 'PROFILE_NOT_FOUND' using errcode = 'P0001';
  end if;

  insert into public.credit_transactions (
    user_id,
    amount,
    type,
    source,
    reference_id,
    metadata
  )
  values (
    v_order.user_id,
    v_order.credits,
    'purchase',
    'alipay_webpay',
    v_order.id,
    jsonb_build_object(
      'alipayTradeNo', p_trade_no,
      'productId', v_order.product_id
    )
  );

  update public.payment_orders as po
     set status = 'completed',
         provider_trade_no = p_trade_no,
         completed_at = coalesce(po.completed_at, now()),
         metadata = po.metadata || jsonb_build_object('paidAmountCents', p_paid_amount_cents)
   where po.id = v_order.id;

  return query select true, v_balance;
end;
$$;

create or replace function public.prepare_alipay_credit_pack_refund(
  p_order_id uuid,
  p_refund_request_no text
)
returns table (
  refund_request_no text,
  refund_amount_cents integer,
  already_prepared boolean
)
language plpgsql
security definer
set search_path = public
as $$
declare
  v_order public.payment_orders%rowtype;
  v_balance integer;
begin
  select po.*
    into v_order
    from public.payment_orders as po
   where po.id = p_order_id
   for update;

  if not found then
    raise exception 'PAYMENT_ORDER_NOT_FOUND' using errcode = 'P0001';
  end if;

  if v_order.payment_provider <> 'alipay' or v_order.product_type <> 'credit_pack' then
    raise exception 'INVALID_ALIPAY_ORDER' using errcode = 'P0001';
  end if;

  if p_refund_request_no is null or btrim(p_refund_request_no) = '' then
    raise exception 'REFUND_REQUEST_NO_REQUIRED' using errcode = 'P0001';
  end if;

  if v_order.status in ('refund_pending', 'refunded') then
    if v_order.provider_refund_request_no <> p_refund_request_no then
      raise exception 'REFUND_REQUEST_NO_MISMATCH' using errcode = 'P0001';
    end if;
    return query select p_refund_request_no, v_order.amount_cents, true;
    return;
  end if;

  if v_order.status <> 'completed' then
    raise exception 'ALIPAY_ORDER_NOT_REFUNDABLE' using errcode = 'P0001';
  end if;

  select p.credit_balance
    into v_balance
    from public.profiles as p
   where p.id = v_order.user_id
   for update;

  if not found then
    raise exception 'PROFILE_NOT_FOUND' using errcode = 'P0001';
  end if;

  if v_balance < v_order.credits then
    raise exception 'PURCHASED_CREDITS_ALREADY_USED' using errcode = 'P0001';
  end if;

  update public.profiles as p
     set credit_balance = p.credit_balance - v_order.credits
   where p.id = v_order.user_id;

  insert into public.credit_transactions (
    user_id,
    amount,
    type,
    source,
    reference_id,
    metadata
  )
  values (
    v_order.user_id,
    -v_order.credits,
    'refund',
    'alipay_webpay_refund_reserve',
    v_order.id,
    jsonb_build_object(
      'refundRequestNo', p_refund_request_no,
      'productId', v_order.product_id
    )
  );

  update public.payment_orders as po
     set status = 'refund_pending',
         provider_refund_request_no = p_refund_request_no,
         metadata = po.metadata || jsonb_build_object('refundPreparedAt', now())
   where po.id = v_order.id;

  return query select p_refund_request_no, v_order.amount_cents, false;
end;
$$;

create or replace function public.finalize_alipay_credit_pack_refund(
  p_order_id uuid,
  p_trade_no text,
  p_refund_amount_cents integer
)
returns table (
  finalized boolean
)
language plpgsql
security definer
set search_path = public
as $$
declare
  v_order public.payment_orders%rowtype;
begin
  select po.*
    into v_order
    from public.payment_orders as po
   where po.id = p_order_id
   for update;

  if not found then
    raise exception 'PAYMENT_ORDER_NOT_FOUND' using errcode = 'P0001';
  end if;

  if v_order.payment_provider <> 'alipay' or v_order.product_type <> 'credit_pack' then
    raise exception 'INVALID_ALIPAY_ORDER' using errcode = 'P0001';
  end if;

  if v_order.provider_trade_no is not null and v_order.provider_trade_no <> p_trade_no then
    raise exception 'ALIPAY_TRADE_NO_MISMATCH' using errcode = 'P0001';
  end if;

  if p_refund_amount_cents is null or p_refund_amount_cents <> v_order.amount_cents then
    raise exception 'ALIPAY_REFUND_AMOUNT_MISMATCH' using errcode = 'P0001';
  end if;

  if v_order.status = 'refunded' then
    return query select false;
    return;
  end if;

  if v_order.status <> 'refund_pending' then
    raise exception 'ALIPAY_REFUND_NOT_PREPARED' using errcode = 'P0001';
  end if;

  update public.payment_orders as po
     set status = 'refunded',
         metadata = po.metadata || jsonb_build_object(
           'refundedAt', now(),
           'refundAmountCents', p_refund_amount_cents
         )
   where po.id = v_order.id;

  return query select true;
end;
$$;

revoke execute on function public.complete_alipay_credit_pack_order(uuid, text, integer, text, jsonb)
  from public, anon, authenticated;
revoke execute on function public.prepare_alipay_credit_pack_refund(uuid, text)
  from public, anon, authenticated;
revoke execute on function public.finalize_alipay_credit_pack_refund(uuid, text, integer)
  from public, anon, authenticated;

grant execute on function public.complete_alipay_credit_pack_order(uuid, text, integer, text, jsonb)
  to service_role;
grant execute on function public.prepare_alipay_credit_pack_refund(uuid, text)
  to service_role;
grant execute on function public.finalize_alipay_credit_pack_refund(uuid, text, integer)
  to service_role;
