class Voice::CallStatus::Manager
  pattr_initialize [:call!]

  def process_status_update(status, duration: nil, timestamp: nil)
    return unless Call::STATUSES.include?(status)
    return if call.status == status
    # Don't overwrite a terminal status — Twilio's late `completed` events would
    # otherwise clobber an agent-rejection reason.
    return if Call::TERMINAL_STATUSES.include?(call.status)

    apply_call_updates!(status, duration: duration, timestamp: timestamp)
    call.conversation.update!(last_activity_at: Time.zone.now)
    # Bump updated_at so the message.updated dispatcher rebroadcasts with the fresh Call embedded.
    call.message&.touch # rubocop:disable Rails/SkipsModelValidations
  end

  private

  def apply_call_updates!(status, duration:, timestamp:)
    attrs = { status: status }
    ts = timestamp || now_seconds

    if status == 'in_progress'
      # Twilio can emit multiple in-progress updates (answered + in-progress, retries).
      # Keep the earliest timestamp so duration_seconds doesn't shift forward.
      started_at = Time.zone.at(ts)
      attrs[:started_at] = started_at if call.started_at.nil? || started_at < call.started_at
    elsif Call::TERMINAL_STATUSES.include?(status)
      call.ended_at = ts
      attrs[:meta] = call.meta
      attrs[:duration_seconds] = resolved_duration(duration, ts)
    end

    call.update!(attrs)
  end

  def resolved_duration(provided_duration, timestamp)
    return provided_duration if provided_duration
    return unless call.started_at

    [timestamp - call.started_at.to_i, 0].max
  end

  def now_seconds
    Time.zone.now.to_i
  end
end
