begin;

-- Candidate cards explicitly allow the user to change an already accepted
-- minute. Keep same-time retries idempotent, but permit a different minute
-- from the same live result only while the Case is still candidate_accepted
-- and the profile still matches the previous user-accepted selection.
create or replace function public.accept_agentic_rectification_candidate_for_case(
  p_user_id uuid,
  p_case_id uuid,
  p_result_id uuid,
  p_time time without time zone
)
returns jsonb
language plpgsql
security definer
set search_path = ''
as $$
declare
  v_case public.agentic_rectification_cases%rowtype;
  v_result public.agentic_rectification_results%rowtype;
  v_profile public.profiles%rowtype;
  v_snapshot jsonb;
  v_selection_kind text;
  v_status text;
begin
  if p_user_id is null or p_case_id is null or p_result_id is null or p_time is null
    or extract(second from p_time) is distinct from 0 then
    raise exception 'agentic_rectification_candidate_invalid_input' using errcode = 'P0001';
  end if;

  select * into v_case
  from public.agentic_rectification_cases
  where id = p_case_id and user_id = p_user_id
  for update;
  if not found then
    raise exception 'agentic_rectification_case_not_found' using errcode = 'P0001';
  end if;
  if v_case.status in ('confirmed', 'closed', 'abandoned', 'superseded') then
    raise exception 'agentic_rectification_case_terminal' using errcode = 'P0001';
  end if;

  select * into v_result
  from public.agentic_rectification_results
  where id = p_result_id
    and user_id = p_user_id
    and case_id = p_case_id
  for update;
  if not found then
    raise exception 'agentic_rectification_candidate_not_found' using errcode = 'P0001';
  end if;
  if v_result.invalidated_at is not null or v_result.expires_at <= pg_catalog.now() then
    raise exception 'agentic_rectification_candidate_expired' using errcode = 'P0001';
  end if;
  if not v_result.selection_allowed then
    raise exception 'agentic_rectification_candidate_selection_blocked' using errcode = 'P0001';
  end if;
  if not exists (
    select 1
    from pg_catalog.jsonb_array_elements(v_result.candidates) candidate
    where candidate ->> 'time' = pg_catalog.to_char(p_time, 'HH24:MI')
  ) then
    raise exception 'agentic_rectification_candidate_time_not_allowed' using errcode = 'P0001';
  end if;

  if v_result.selected_time is not null then
    select * into v_profile
    from public.profiles
    where id = p_user_id
    for update;

    -- Same candidate and minute is a retry/double-click, not a new choice.
    if v_result.selected_time is not distinct from p_time
      and v_case.accepted_time is not distinct from p_time then
      if not found
        or v_profile.active_birth_time is distinct from v_result.selected_time
        or v_profile.birth_time is distinct from v_result.selected_time
        or v_profile.birth_time_status is distinct from (
          case when v_result.selection_kind = 'engine_confirmed' then 'confirmed' else 'accepted' end
        ) then
        raise exception 'agentic_rectification_candidate_profile_changed' using errcode = 'P0001';
      end if;
      return jsonb_build_object(
        'success', true,
        'saved_time', pg_catalog.to_char(p_time, 'HH24:MI'),
        'status', case when v_result.selection_kind = 'engine_confirmed' then 'confirmed' else 'accepted' end,
        'result_id', v_result.id,
        'case_status', v_case.status,
        'idempotent', true
      );
    end if;

    -- A card-driven switch is legal only for an unconfirmed user acceptance.
    -- It must never demote a confirmed result or mutate a non-selection Case.
    if v_case.status is distinct from 'candidate_accepted'
      or v_case.accepted_time is null
      or v_case.accepted_time is distinct from v_result.selected_time
      or v_case.confirmed_time is not null
      or v_result.selection_kind is distinct from 'user_accepted' then
      raise exception 'agentic_rectification_candidate_selection_blocked' using errcode = 'P0001';
    end if;

    -- The profile must still reflect the old accepted minute. Any independent
    -- profile edit invalidates the switch instead of silently overwriting it.
    if not found
      or v_profile.active_birth_time is distinct from v_result.selected_time
      or v_profile.birth_time is distinct from v_result.selected_time
      or v_profile.birth_time_status is distinct from 'accepted' then
      raise exception 'agentic_rectification_candidate_profile_changed' using errcode = 'P0001';
    end if;

    if exists (
      select 1
      from public.agentic_rectification_results newer
      where newer.user_id = p_user_id
        and newer.case_id = p_case_id
        and newer.invalidated_at is null
        and newer.created_at > v_result.created_at
    ) then
      raise exception 'agentic_rectification_candidate_superseded' using errcode = 'P0001';
    end if;

    update public.profiles
    set active_birth_time = p_time,
        birth_time = p_time,
        birth_time_status = 'accepted',
        rectification_confidence = case
          when v_result.overall_confidence = 'high' then 100
          when v_result.overall_confidence = 'medium' then 70
          else 40
        end,
        updated_at = pg_catalog.now()
    where id = p_user_id;

    update public.agentic_rectification_results
    set selected_time = p_time,
        selection_kind = 'user_accepted',
        selected_at = pg_catalog.now(),
        updated_at = pg_catalog.now()
    where id = v_result.id;

    -- The profile trigger temporarily marks resumable cases needs_rebaseline;
    -- this atomic candidate switch is the intended accepted state and wins.
    update public.agentic_rectification_cases
    set status = 'candidate_accepted',
        accepted_time = p_time,
        last_activity_at = pg_catalog.now(),
        updated_at = pg_catalog.now()
    where id = v_case.id;

    return jsonb_build_object(
      'success', true,
      'saved_time', pg_catalog.to_char(p_time, 'HH24:MI'),
      'status', 'accepted',
      'result_id', v_result.id,
      'case_status', 'candidate_accepted',
      'idempotent', false
    );
  end if;

  -- Fresh acceptance still requires the profile to match the Case baseline.
  v_snapshot := v_case.baseline_birth_snapshot;
  select * into v_profile
  from public.profiles
  where id = p_user_id
  for update;

  if not found
    or v_profile.birth_date is distinct from (v_snapshot ->> 'birth_date')::date
    or v_profile.reported_birth_time is distinct from (v_snapshot ->> 'reported_birth_time')::time without time zone
    or v_profile.active_birth_time is distinct from (v_snapshot ->> 'active_birth_time')::time without time zone
    or v_profile.birth_time_source is distinct from v_snapshot ->> 'birth_time_source'
    or v_profile.birth_time_period is distinct from v_snapshot ->> 'birth_time_period'
    or v_profile.uncertainty_before_minutes is distinct from (v_snapshot ->> 'uncertainty_before_minutes')::integer
    or v_profile.uncertainty_after_minutes is distinct from (v_snapshot ->> 'uncertainty_after_minutes')::integer
    or v_profile.latitude is distinct from (v_snapshot ->> 'latitude')::double precision
    or v_profile.longitude is distinct from (v_snapshot ->> 'longitude')::double precision
    or v_profile.timezone_offset is distinct from (v_snapshot ->> 'timezone_offset')::double precision then
    raise exception 'agentic_rectification_candidate_profile_changed' using errcode = 'P0001';
  end if;

  if exists (
    select 1
    from public.agentic_rectification_results newer
    where newer.user_id = p_user_id
      and newer.case_id = p_case_id
      and newer.invalidated_at is null
      and newer.created_at > v_result.created_at
  ) then
    raise exception 'agentic_rectification_candidate_superseded' using errcode = 'P0001';
  end if;

  v_selection_kind := case
    when v_result.confirmation_allowed
      and v_result.representative_time is not distinct from p_time
      then 'engine_confirmed'
    else 'user_accepted'
  end;
  v_status := case when v_selection_kind = 'engine_confirmed' then 'confirmed' else 'accepted' end;

  update public.profiles
  set active_birth_time = p_time,
      birth_time = p_time,
      birth_time_status = v_status,
      rectification_confidence = case
        when v_result.overall_confidence = 'high' then 100
        when v_result.overall_confidence = 'medium' then 70
        else 40
      end,
      updated_at = pg_catalog.now()
  where id = p_user_id;

  update public.agentic_rectification_results
  set selected_time = p_time,
      selection_kind = v_selection_kind,
      selected_at = pg_catalog.now(),
      updated_at = pg_catalog.now()
  where id = v_result.id;

  update public.agentic_rectification_results
  set invalidated_at = pg_catalog.now(),
      updated_at = pg_catalog.now()
  where user_id = p_user_id
    and case_id = p_case_id
    and id <> v_result.id
    and invalidated_at is null
    and selected_time is null;

  update public.agentic_rectification_cases
  set status = 'candidate_accepted',
      accepted_time = p_time,
      last_activity_at = pg_catalog.now(),
      updated_at = pg_catalog.now()
  where id = v_case.id;

  return jsonb_build_object(
    'success', true,
    'saved_time', pg_catalog.to_char(p_time, 'HH24:MI'),
    'status', v_status,
    'result_id', v_result.id,
    'case_status', 'candidate_accepted',
    'idempotent', false
  );
end;
$$;

revoke all on function public.accept_agentic_rectification_candidate_for_case(uuid, uuid, uuid, time without time zone)
  from public, anon, authenticated;
grant execute on function public.accept_agentic_rectification_candidate_for_case(uuid, uuid, uuid, time without time zone)
  to service_role;

commit;
