create extension if not exists pgcrypto;

create or replace function public.get_delivery_invitation_by_token(p_token text)
returns jsonb
language plpgsql
security definer
set search_path = public, extensions
as $$
declare
  v_invitation public.delivery_invitations%rowtype;
  v_group public.order_groups%rowtype;
  v_order record;
  v_token_hash text;
  v_state text;
  v_order_number text;
  v_customer_name text;
  v_customer_phone text;
  v_order_items jsonb;
  v_order_numbers jsonb;
  v_now timestamptz := timezone('utc', now());
begin
  if nullif(trim(coalesce(p_token, '')), '') is null then
    raise exception 'token is required';
  end if;

  v_token_hash := encode(digest(p_token, 'sha256'), 'hex');

  select *
  into v_invitation
  from public.delivery_invitations
  where token_hash = v_token_hash;

  if not found then
    raise exception 'Invitation not found';
  end if;

  if v_invitation.revoked_at is not null then
    raise exception 'Invitation expired';
  end if;

  if v_invitation.expires_at is not null and v_invitation.expires_at <= v_now then
    raise exception 'Invitation expired';
  end if;

  if v_invitation.order_group_id is not null then
    select *
    into v_group
    from public.order_groups
    where id = v_invitation.order_group_id;

    if not found then
      raise exception 'Order group not found';
    end if;

    v_state := case
      when v_group.delivery_status = 'agreed' then 'agreed'
      when v_group.delivery_status = 'delivered' then 'delivered'
      when v_invitation.state in ('awaiting_choice', 'opened', 'reminder_sent') then v_invitation.state
      else 'default'
    end;

    update public.delivery_invitations
    set
      opened_at = case
        when v_state in ('awaiting_choice', 'opened', 'reminder_sent') and opened_at is null then v_now
        else opened_at
      end,
      access_count = coalesce(access_count, 0) + 1,
      last_accessed_at = v_now
    where id = v_invitation.id
    returning * into v_invitation;

    v_order_number := coalesce(
      nullif(v_invitation.order_number, ''),
      to_jsonb(v_group.order_numbers) ->> 0,
      nullif(v_group.group_key, '')
    );
    v_customer_name := coalesce(
      nullif(v_group.customer_name, ''),
      nullif(v_invitation.customer_name, '')
    );
    v_customer_phone := coalesce(
      nullif(v_group.customer_phone, ''),
      nullif(v_group.customer_phone_normalized, ''),
      nullif(v_invitation.customer_phone, '')
    );
    select coalesce(
      jsonb_agg(jsonb_build_object('name', order_number, 'quantity', '')),
      '[]'::jsonb
    )
    into v_order_items
    from jsonb_array_elements_text(
      case
        when jsonb_typeof(to_jsonb(v_group.order_numbers)) = 'array' then to_jsonb(v_group.order_numbers)
        else '[]'::jsonb
      end
    ) as order_number;

    return jsonb_build_object(
      'ok', true,
      'invitation', jsonb_build_object(
        'orderId', coalesce(v_invitation.order_group_id, v_group.id)::text,
        'orderGroupId', coalesce(v_invitation.order_group_id, v_group.id)::text,
        'state', v_state,
        'token', p_token,
        'orderNumber', v_order_number,
        'customerName', v_customer_name,
        'customerPhone', v_customer_phone,
        'orderItems', v_order_items,
        'availableSlots', coalesce(to_jsonb(v_invitation.available_slots), '[]'::jsonb),
        'deliveryDate', v_invitation.delivery_date,
        'deliveryTime', v_invitation.delivery_time,
        'orderStatus', null,
        'deliveryAgreementStatus', null
      )
    );
  end if;

  select id, order_number, status, delivery_agreement_status, customer
  into v_order
  from public.orders
  where id = v_invitation.order_id;

  if not found then
    raise exception 'Order not found';
  end if;

  v_state := case v_order.status
    when 'Ожидает ответа клиента' then 'awaiting_choice'
    when 'Ожидает согласования доставки' then 'opened'
    when 'Напоминание отправлено' then 'reminder_sent'
    when 'Переход отправлен' then 'reminder_sent'
    when 'Передан логисту' then 'transferred_to_logistics'
    when 'Платное хранение' then 'paid_storage'
    when 'Доставлен' then 'delivered'
    when 'Доставка согласована' then 'agreed'
    else 'default'
  end;

  update public.delivery_invitations
  set
    opened_at = case
      when v_state in ('awaiting_choice', 'opened', 'reminder_sent') and opened_at is null then v_now
      else opened_at
    end,
    access_count = coalesce(access_count, 0) + 1,
    last_accessed_at = v_now
    where id = v_invitation.id
    returning * into v_invitation;

  v_order_items := case
    when jsonb_typeof(v_order.customer -> 'items') = 'array' then v_order.customer -> 'items'
    else '[]'::jsonb
  end;

  return jsonb_build_object(
    'ok', true,
    'invitation', jsonb_build_object(
      'orderId', v_invitation.order_id::text,
      'state', v_state,
      'token', p_token,
      'orderNumber', coalesce(nullif(v_order.order_number, ''), nullif(v_invitation.order_number, '')),
      'customerName', coalesce(nullif(v_order.customer ->> 'name', ''), nullif(v_invitation.customer_name, '')),
      'customerPhone', coalesce(nullif(v_order.customer ->> 'phone', ''), nullif(v_invitation.customer_phone, '')),
      'orderItems', v_order_items,
      'availableSlots', coalesce(to_jsonb(v_invitation.available_slots), '[]'::jsonb),
      'deliveryDate', v_invitation.delivery_date,
      'deliveryTime', v_invitation.delivery_time,
      'orderStatus', v_order.status,
      'deliveryAgreementStatus', v_order.delivery_agreement_status
    )
  );
end;
$$;

create or replace function public.confirm_delivery_choice_by_token(
  p_token text,
  p_delivery_date date,
  p_delivery_time text
)
returns jsonb
language plpgsql
security definer
set search_path = public, extensions
as $$
declare
  v_invitation public.delivery_invitations%rowtype;
  v_group public.order_groups%rowtype;
  v_order record;
  v_token_hash text;
  v_slot_label text;
  v_now timestamptz := timezone('utc', now());
begin
  if nullif(trim(coalesce(p_token, '')), '') is null then
    raise exception 'token is required';
  end if;

  if p_delivery_date is null or nullif(trim(coalesce(p_delivery_time, '')), '') is null then
    raise exception 'Selected slot is not available';
  end if;

  v_token_hash := encode(digest(p_token, 'sha256'), 'hex');
  v_slot_label := concat(p_delivery_date::text, ', ', trim(p_delivery_time));

  select *
  into v_invitation
  from public.delivery_invitations
  where token_hash = v_token_hash
  for update;

  if not found then
    raise exception 'Invitation not found';
  end if;

  if v_invitation.revoked_at is not null then
    raise exception 'Invitation expired';
  end if;

  if v_invitation.expires_at is not null and v_invitation.expires_at <= v_now then
    raise exception 'Invitation expired';
  end if;

  if v_invitation.state not in ('awaiting_choice', 'opened', 'reminder_sent') then
    raise exception 'Invitation is no longer active';
  end if;

  if cardinality(v_invitation.available_slots) > 0 and not (v_slot_label = any(v_invitation.available_slots)) then
    raise exception 'Selected slot is not available';
  end if;

  if v_invitation.order_group_id is not null then
    select *
    into v_group
    from public.order_groups
    where id = v_invitation.order_group_id
    for update;

    if not found then
      raise exception 'Order group not found';
    end if;

    if v_group.delivery_status <> 'pending_confirmation' then
      raise exception 'Invitation is no longer active';
    end if;

    update public.delivery_invitations
    set
      state = 'agreed',
      delivery_date = p_delivery_date,
      delivery_time = trim(p_delivery_time),
      confirmed_at = v_now,
      access_count = coalesce(access_count, 0) + 1,
      last_accessed_at = v_now
    where id = v_invitation.id;

    update public.order_groups
    set
      delivery_status = 'agreed',
      delivery_date = p_delivery_date,
      delivery_time = trim(p_delivery_time),
      notification_status = 'confirmed',
      updated_at = v_now
    where id = v_group.id;

    insert into public.integration_events (
      order_id,
      event_type,
      direction,
      status,
      payload
    )
    values (
      null,
      'delivery_choice_confirmed',
      'inbound',
      'success',
      jsonb_build_object(
        'order_group_id', v_group.id,
        'delivery_invitation_id', v_invitation.id,
        'delivery_date', p_delivery_date,
        'delivery_time', trim(p_delivery_time)
      )
    );

    return jsonb_build_object(
      'ok', true,
      'orderGroupId', v_group.id,
      'deliveryStatus', 'agreed'
    );
  end if;

  select id, status, delivery_agreement_status
  into v_order
  from public.orders
  where id = v_invitation.order_id
  for update;

  if not found then
    raise exception 'Order not found';
  end if;

  if v_order.status not in ('Ожидает ответа клиента', 'Ожидает согласования доставки') then
    raise exception 'Invitation is no longer active';
  end if;

  update public.delivery_invitations
  set
    state = 'agreed',
    delivery_date = p_delivery_date,
    delivery_time = trim(p_delivery_time),
    confirmed_at = v_now,
    access_count = coalesce(access_count, 0) + 1,
    last_accessed_at = v_now
  where id = v_invitation.id;

  update public.orders
  set
    status = 'Доставка согласована',
    delivery_agreement_status = 'Подтверждено клиентом'
  where id = v_order.id;

  insert into public.delivery_slots (
    order_id,
    delivery_date,
    delivery_time,
    logistician_id,
    status
  )
  values (
    v_order.id,
    p_delivery_date,
    trim(p_delivery_time),
    null,
    'confirmed_by_client'
  );

  insert into public.order_history (
    order_id,
    action,
    old_status,
    new_status,
    metadata
  )
  values (
    v_order.id,
    'Подтверждение выбора доставки клиентом',
    v_order.status,
    'Доставка согласована',
    jsonb_build_object(
      'old_delivery_agreement_status', v_order.delivery_agreement_status,
      'new_delivery_agreement_status', 'Подтверждено клиентом',
      'delivery_date', p_delivery_date,
      'delivery_time', trim(p_delivery_time)
    )
  );

  insert into public.integration_events (
    order_id,
    event_type,
    direction,
    status,
    payload
  )
  values (
    v_order.id,
    'delivery_choice_confirmed',
    'inbound',
    'success',
    jsonb_build_object(
      'delivery_date', p_delivery_date,
      'delivery_time', trim(p_delivery_time)
    )
  );

  return jsonb_build_object(
    'ok', true,
    'orderId', v_order.id,
    'status', 'Доставка согласована',
    'deliveryAgreementStatus', 'Подтверждено клиентом'
  );
end;
$$;

revoke all on function public.get_delivery_invitation_by_token(text) from public;
grant execute on function public.get_delivery_invitation_by_token(text) to anon, authenticated;

revoke all on function public.confirm_delivery_choice_by_token(text, date, text) from public;
grant execute on function public.confirm_delivery_choice_by_token(text, date, text) to anon, authenticated;
